Merge "telecom register-sim-phone-account for secondary users."
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 122c2de..be9b1dd 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -299,6 +299,7 @@
             new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
     private final HeadsetMediaButton mHeadsetMediaButton;
     private final WiredHeadsetManager mWiredHeadsetManager;
+    private final SystemStateHelper mSystemStateHelper;
     private final BluetoothRouteManager mBluetoothRouteManager;
     private final DockManager mDockManager;
     private final TtyManager mTtyManager;
@@ -436,6 +437,7 @@
         mMissedCallNotifier = missedCallNotifier;
         StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
         mWiredHeadsetManager = wiredHeadsetManager;
+        mSystemStateHelper = systemStateHelper;
         mDefaultDialerCache = defaultDialerCache;
         mBluetoothRouteManager = bluetoothManager;
         mDockManager = new DockManager(context);
@@ -1605,6 +1607,57 @@
         return targetPhoneAccount != null && targetPhoneAccount.isSelfManaged();
     }
 
+    public void onCallRedirectionComplete(Call call, Uri handle,
+                                          PhoneAccountHandle phoneAccountHandle,
+                                          GatewayInfo gatewayInfo, boolean speakerphoneOn,
+                                          int videoState, boolean shouldCancelCall,
+                                          String uiAction) {
+        Log.i(this, "onCallRedirectionComplete for Call %s with handle %s" +
+                        " and phoneAccountHandle %s", call, handle, phoneAccountHandle);
+
+        boolean endEarly = false;
+        String disconnectReason = "";
+
+        if (shouldCancelCall) {
+            Log.w(this, "onCallRedirectionComplete: call is canceled");
+            endEarly = true;
+            disconnectReason = "Canceled from Call Redirection Service";
+            // TODO show UI uiAction is CallRedirectionProcessor#UI_TYPE_USER_DEFINED_TIMEOUT
+        } else if (handle == null) {
+            Log.w(this, "onCallRedirectionComplete: handle is null");
+            endEarly = true;
+            disconnectReason = "Null handle from Call Redirection Service";
+        } else if (phoneAccountHandle == null) {
+            Log.w(this, "onCallRedirectionComplete: phoneAccountHandle is null");
+            endEarly = true;
+            disconnectReason = "Null phoneAccountHandle from Call Redirection Service";
+        } else if (mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(mContext,
+                handle.getSchemeSpecificPart())) {
+            Log.w(this, "onCallRedirectionComplete: emergency number %s is redirected from Call"
+                    + " Redirection Service", handle.getSchemeSpecificPart());
+            endEarly = true;
+            disconnectReason = "Emergency number is redirected from Call Redirection Service";
+        }
+        if (endEarly) {
+            if (call != null) {
+                call.disconnect(disconnectReason);
+            }
+            return;
+        }
+
+        // If this call is already disconnected then we have nothing more to do.
+        if (call.isDisconnected()) {
+            Log.w(this, "onCallRedirectionComplete: Call has already been disconnected,"
+                    + " ignore the call redirection %s", call);
+            return;
+        }
+
+        // TODO show UI uiAction is CallRedirectionProcessor#UI_TYPE_USER_DEFINED_ASK_FOR_CONFIRM
+
+        call.setTargetPhoneAccount(phoneAccountHandle);
+        placeOutgoingCall(call, handle, gatewayInfo, speakerphoneOn, videoState);
+    }
+
     /**
      * Attempts to issue/connect the specified call.
      *
@@ -2589,7 +2642,8 @@
      *
      * @return The {@link PhoneAccountRegistrar}.
      */
-    PhoneAccountRegistrar getPhoneAccountRegistrar() {
+    @VisibleForTesting
+    public PhoneAccountRegistrar getPhoneAccountRegistrar() {
         return mPhoneAccountRegistrar;
     }
 
@@ -3386,6 +3440,14 @@
         return mLock;
     }
 
+    public Timeouts.Adapter getTimeoutsAdapter() {
+        return mTimeoutsAdapter;
+    }
+
+    public SystemStateHelper getSystemStateHelper() {
+        return mSystemStateHelper;
+    }
+
     private void reloadMissedCallsOfUser(UserHandle userHandle) {
         mMissedCallNotifier.reloadFromDatabase(mCallerInfoLookupHelper,
                 new MissedCallNotifier.CallInfoFactory(), userHandle);
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 5571fcf..ee48f77 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -1226,7 +1226,8 @@
                         serviceInfo.metaData.getBoolean(
                                 TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, false);
 
-                int currentType = getInCallServiceType(entry.serviceInfo, packageManager);
+                int currentType = getInCallServiceType(entry.serviceInfo, packageManager,
+                        packageName);
                 if (requestedType == 0 || requestedType == currentType) {
                     if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
                         // We enforce the rule that self-managed calls are not supported by non-ui
@@ -1250,7 +1251,8 @@
     /**
      * Returns the type of InCallService described by the specified serviceInfo.
      */
-    private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) {
+    private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager,
+            String packageName) {
         // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which
         // enforces that only Telecom can bind to it.
         boolean hasServiceBindPermission = serviceInfo.permission != null &&
@@ -1268,12 +1270,12 @@
         }
 
         // Check to see if the service holds permissions or metadata for third party apps.
-        boolean hasInCallServiceUIMetadata = serviceInfo.metaData != null &&
-                serviceInfo.metaData.containsKey(TelecomManager.METADATA_IN_CALL_SERVICE_UI);
+        boolean isUIService = serviceInfo.metaData != null &&
+                serviceInfo.metaData.getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_UI);
         boolean isThirdPartyCompanionApp = packageManager.checkPermission(
                 Manifest.permission.CALL_COMPANION_APP,
                 serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED &&
-                !hasInCallServiceUIMetadata;
+                !isUIService;
 
         // Check to see if the service is a car-mode UI type by checking that it has the
         // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the
@@ -1286,16 +1288,17 @@
                         TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) &&
                 (hasControlInCallPermission || isThirdPartyCompanionApp);
         if (isCarModeUIService) {
-            return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
+            // ThirdPartyInCallService shouldn't be used when role manager hasn't assigned any car
+            // mode role holders, i.e. packageName is null.
+            if (isUIService || (isThirdPartyCompanionApp && packageName != null)) {
+                return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
+            }
         }
 
         // Check to see that it is the default dialer package
         boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
                 mDefaultDialerCache.getDefaultDialerApplication(
                     mCallsManager.getCurrentUserHandle().getIdentifier()));
-        boolean isUIService = serviceInfo.metaData != null &&
-                serviceInfo.metaData.getBoolean(
-                        TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
         if (isDefaultDialerPackage && isUIService) {
             return IN_CALL_SERVICE_TYPE_DIALER_UI;
         }
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index d0afe28..5864ce0 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -16,10 +16,13 @@
 
 package com.android.server.telecom;
 
+import android.annotation.Nullable;
+import android.content.Context;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.MediaPlayer;
 import android.media.ToneGenerator;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
 import android.telecom.Log;
@@ -87,33 +90,51 @@
     public static class MediaPlayerAdapterImpl implements MediaPlayerAdapter {
         private MediaPlayer mMediaPlayer;
 
-        public MediaPlayerAdapterImpl(MediaPlayer mediaPlayer) {
+        /**
+         * Create new media player adapter backed by a real mediaplayer.
+         * Note: Its possible for the mediaplayer to be null if
+         * {@link MediaPlayer#create(Context, Uri)} fails for some reason; in this case we can
+         * continue but not bother playing the audio.
+         * @param mediaPlayer The media player.
+         */
+        public MediaPlayerAdapterImpl(@Nullable MediaPlayer mediaPlayer) {
             mMediaPlayer = mediaPlayer;
         }
 
         @Override
         public void setLooping(boolean isLooping) {
-            mMediaPlayer.setLooping(isLooping);
+            if (mMediaPlayer != null) {
+                mMediaPlayer.setLooping(isLooping);
+            }
         }
 
         @Override
         public void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
-            mMediaPlayer.setOnCompletionListener(listener);
+            if (mMediaPlayer != null) {
+                mMediaPlayer.setOnCompletionListener(listener);
+            }
         }
 
         @Override
         public void start() {
-            mMediaPlayer.start();
+            if (mMediaPlayer != null) {
+                mMediaPlayer.start();
+            }
         }
 
         @Override
         public void release() {
-            mMediaPlayer.release();
+            if (mMediaPlayer != null) {
+                mMediaPlayer.release();
+            }
         }
 
         @Override
         public int getDuration() {
-            return mMediaPlayer.getDuration();
+            if (mMediaPlayer != null) {
+                return mMediaPlayer.getDuration();
+            }
+            return 0;
         }
     }
 
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 3797c68..440bb10 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -37,6 +37,7 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.callredirection.CallRedirectionProcessor;
 
 // TODO: Needed for move to system service: import com.android.internal.R;
 
@@ -240,6 +241,8 @@
         boolean callImmediately = false;
         // True for all managed calls, false for self-managed calls.
         boolean sendNewOutgoingCallBroadcast = true;
+        // True for requesting call redirection, false for not requesting it.
+        boolean requestCallRedirection = true;
         Uri callingAddress = handle;
 
         if (!isSelfManaged) {
@@ -294,6 +297,7 @@
             // Self-managed call.
             callImmediately = true;
             sendNewOutgoingCallBroadcast = false;
+            requestCallRedirection = false;
             Log.i(this, "Skipping NewOutgoingCallBroadcast for self-managed call.");
         }
 
@@ -312,10 +316,33 @@
             // initiate the call again because of the presence of the EXTRA_ALREADY_CALLED extra.
         }
 
+        boolean callRedirectionWithService = false;
+        if (requestCallRedirection) {
+            CallRedirectionProcessor callRedirectionProcessor = new CallRedirectionProcessor(
+                    mContext, mCallsManager, mCall, callingAddress,
+                    mCallsManager.getPhoneAccountRegistrar(),
+                    getGateWayInfoFromIntent(intent, handle),
+                    intent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
+                            false),
+                    intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                            VideoProfile.STATE_AUDIO_ONLY));
+            /**
+             * If there is an available {@link android.telecom.CallRedirectionService}, use the
+             * {@link CallRedirectionProcessor} to perform call redirection instead of using
+             * broadcasting.
+             */
+            callRedirectionWithService = callRedirectionProcessor
+                    .canMakeCallRedirectionWithService();
+            if (callRedirectionWithService) {
+                callRedirectionProcessor.performCallRedirection();
+            }
+        }
+
         if (sendNewOutgoingCallBroadcast) {
             UserHandle targetUser = mCall.getInitiatingUser();
             Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
-            broadcastIntent(intent, number, !callImmediately, targetUser);
+            broadcastIntent(intent, number,
+                    !callImmediately && !callRedirectionWithService, targetUser);
         }
         return DisconnectCause.NOT_DISCONNECTED;
     }
diff --git a/src/com/android/server/telecom/RoleManagerAdapterImpl.java b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
index 873a8d1..33055a8 100644
--- a/src/com/android/server/telecom/RoleManagerAdapterImpl.java
+++ b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
@@ -26,15 +26,11 @@
 import java.util.stream.Collectors;
 
 public class RoleManagerAdapterImpl implements RoleManagerAdapter {
-    // TODO: replace with actual role manager const.
     private static final String ROLE_CALL_REDIRECTION_APP = "android.app.role.PROXY_CALLING_APP";
-    // TODO: replace with actual role manager const.
-    private static final String ROLE_CAR_MODE_DIALER = "android.app.role.ROLE_CAR_MODE_DIALER";
-    // TODO: replace with actual role manager const.
+    private static final String ROLE_CAR_MODE_DIALER = "android.app.role.CAR_MODE_DIALER_APP";
     private static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING_APP";
-    // TODO: replace with actual role manager const.
     private static final String ROLE_CALL_COMPANION_APP =
-            "android.app.role.ROLE_CALL_COMPANION_APP";
+            "android.app.role.CALL_COMPANION_APP";
 
     private String mOverrideDefaultCallRedirectionApp = null;
     private String mOverrideDefaultCallScreeningApp = null;
@@ -165,7 +161,7 @@
         pw.println();
 
         pw.print("DefaultCarModeDialerApp: ");
-        if (mOverrideDefaultCallScreeningApp != null) {
+        if (mOverrideDefaultCarModeApp != null) {
             pw.print("(override ");
             pw.print(mOverrideDefaultCarModeApp);
             pw.print(") ");
@@ -174,7 +170,7 @@
         pw.println();
 
         pw.print("DefaultCallCompanionApps: ");
-        if (mOverrideDefaultCallScreeningApp != null) {
+        if (mOverrideCallCompanionApps != null) {
             pw.print("(override ");
             pw.print(mOverrideCallCompanionApps.stream().collect(Collectors.joining(", ")));
             pw.print(") ");
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index e927694..37f9363 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -187,7 +187,7 @@
      */
     public static long getUserDefinedCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
         return get(contentResolver, "user_defined_call_redirection_timeout",
-            3000L /* 3 seconds */);
+            5000L /* 5 seconds */);
     }
 
     /**
@@ -196,6 +196,6 @@
      * @param contentResolver The content resolved.
      */
     public static long getCarrierCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
-        return get(contentResolver, "carrier_call_redirection_timeout", 3000L /* 3 seconds */);
+        return get(contentResolver, "carrier_call_redirection_timeout", 5000L /* 5 seconds */);
     }
 }
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
index abb87e1..28ecbc7 100644
--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
@@ -16,26 +16,22 @@
 
 package com.android.server.telecom.callredirection;
 
-import android.Manifest;
-import android.app.AppOpsManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.telecom.CallRedirectionService;
+import android.telecom.GatewayInfo;
 import android.telecom.Log;
 import android.telecom.Logging.Runnable;
 import android.telecom.PhoneAccountHandle;
-import android.telephony.CarrierConfigManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.ICallRedirectionAdapter;
 import com.android.internal.telecom.ICallRedirectionService;
@@ -46,8 +42,6 @@
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
 
-import java.util.List;
-
 /**
  * A single instance of call redirection processor that handles the call redirection with
  * user-defined {@link CallRedirectionService} and carrier {@link CallRedirectionService} for a
@@ -98,10 +92,14 @@
         private void onServiceBound(ICallRedirectionService service) {
             mService = service;
             try {
-                mService.placeCall(new CallRedirectionAdapter(), mHandle, mPhoneAccountHandle);
+                mHandle = mCallRedirectionProcessorHelper.formatNumberForRedirection(mHandle);
+                // Telecom does not perform user interactions for carrier call redirection.
+                mService.placeCall(new CallRedirectionAdapter(), mHandle, mPhoneAccountHandle,
+                        mAllowInteractiveResponse
+                                && mServiceType.equals(SERVICE_TYPE_USER_DEFINED));
                 Log.addEvent(mCall, mServiceType.equals(SERVICE_TYPE_USER_DEFINED)
                         ? LogUtils.Events.REDIRECTION_SENT_USER
-                        : LogUtils.Events.REDIRECTION_SENT_CARRIER);
+                        : LogUtils.Events.REDIRECTION_SENT_CARRIER, mComponentName);
                 Log.d(this, "Requested placeCall with [handle]" + Log.pii(mHandle)
                         + " [phoneAccountHandle]" + mPhoneAccountHandle);
             } catch (RemoteException e) {
@@ -189,16 +187,20 @@
             }
 
             @Override
-            public void redirectCall(Uri handle, PhoneAccountHandle targetPhoneAccount) {
+            public void redirectCall(Uri handle, PhoneAccountHandle targetPhoneAccount,
+                                     boolean confirmFirst) {
                 Log.startSession("CRA.rC");
                 long token = Binder.clearCallingIdentity();
                 try {
                     synchronized (mTelecomLock) {
                         mHandle = handle;
                         mPhoneAccountHandle = targetPhoneAccount;
+                        mUiAction = (confirmFirst && mServiceType.equals(SERVICE_TYPE_USER_DEFINED)
+                                && mAllowInteractiveResponse)
+                                ? UI_TYPE_USER_DEFINED_ASK_FOR_CONFIRM : mUiAction;
                         Log.d(this, "Received redirectCall with [handle]" + Log.pii(mHandle)
                                 + " [phoneAccountHandle]" + mPhoneAccountHandle + " from "
-                                + mServiceType + " call" + " redirection service");
+                                + mServiceType + " call redirection service");
                         finishCallRedirection();
                     }
                 } finally {
@@ -212,14 +214,23 @@
     private final Context mContext;
     private final CallsManager mCallsManager;
     private final Call mCall;
-    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+    private final boolean mAllowInteractiveResponse;
+    private final GatewayInfo mGatewayInfo;
+    private final boolean mSpeakerphoneOn;
+    private final int mVideoState;
     private final Timeouts.Adapter mTimeoutsAdapter;
     private final TelecomSystem.SyncRoot mTelecomLock;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
     private CallRedirectionAttempt mAttempt;
+    private CallRedirectionProcessorHelper mCallRedirectionProcessorHelper;
+
     public static final String SERVICE_TYPE_CARRIER = "carrier";
     public static final String SERVICE_TYPE_USER_DEFINED = "user_defined";
+    public static final String UI_TYPE_NO_ACTION = "no_action";
+    public static final String UI_TYPE_USER_DEFINED_TIMEOUT = "user_defined_timeout";
+    public static final String UI_TYPE_USER_DEFINED_ASK_FOR_CONFIRM
+            = "user_defined_ask_for_confirm";
 
     private PhoneAccountHandle mPhoneAccountHandle;
     private Uri mHandle;
@@ -229,6 +240,10 @@
      */
     private boolean mShouldCancelCall = false;
     /**
+     * Indicates Telecom should handle different types of UI if need.
+     */
+    private String mUiAction = UI_TYPE_NO_ACTION;
+    /**
      * Indicates if Telecom is waiting for a callback from a user-defined
      * {@link CallRedirectionService}.
      */
@@ -243,19 +258,28 @@
             Context context,
             CallsManager callsManager,
             Call call,
-            PhoneAccountRegistrar phoneAccountRegistrar,
             Uri handle,
-            PhoneAccountHandle phoneAccountHandle,
-            Timeouts.Adapter timeoutsAdapter,
-            TelecomSystem.SyncRoot lock) {
+            PhoneAccountRegistrar phoneAccountRegistrar,
+            GatewayInfo gatewayInfo,
+            boolean speakerphoneOn,
+            int videoState) {
         mContext = context;
         mCallsManager = callsManager;
         mCall = call;
-        mPhoneAccountRegistrar = phoneAccountRegistrar;
         mHandle = handle;
-        mPhoneAccountHandle = phoneAccountHandle;
-        mTimeoutsAdapter = timeoutsAdapter;
-        mTelecomLock = lock;
+        mPhoneAccountHandle = call.getTargetPhoneAccount();
+        mGatewayInfo = gatewayInfo;
+        mSpeakerphoneOn = speakerphoneOn;
+        mVideoState = videoState;
+        mTimeoutsAdapter = callsManager.getTimeoutsAdapter();
+        mTelecomLock = callsManager.getLock();
+        /**
+         * The current rule to decide whether the implemented {@link CallRedirectionService} should
+         * allow interactive responses with users is only based on whether it is in car mode.
+         */
+        mAllowInteractiveResponse = !callsManager.getSystemStateHelper().isCarMode();
+        mCallRedirectionProcessorHelper = new CallRedirectionProcessorHelper(
+                context, callsManager, phoneAccountRegistrar);
     }
 
     @Override
@@ -264,11 +288,15 @@
         mHandler.post(new Runnable("CRP.oCRC", mTelecomLock) {
             @Override
             public void loggedRun() {
+                mHandle = mCallRedirectionProcessorHelper.processNumberWhenRedirectionComplete(
+                        mHandle);
                 if (mIsUserDefinedRedirectionPending) {
                     Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_USER);
                     mIsUserDefinedRedirectionPending = false;
                     if (mShouldCancelCall) {
-                        // TODO mCallsManager.onCallRedirectionComplete
+                        mCallsManager.onCallRedirectionComplete(mCall, mHandle,
+                                mPhoneAccountHandle, mGatewayInfo, mSpeakerphoneOn, mVideoState,
+                                mShouldCancelCall, mUiAction);
                     } else {
                         performCarrierCallRedirection();
                     }
@@ -276,23 +304,33 @@
                 if (mIsCarrierRedirectionPending) {
                     Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_CARRIER);
                     mIsCarrierRedirectionPending = false;
-                    // TODO mCallsManager.onCallRedirectionComplete
+                    mCallsManager.onCallRedirectionComplete(mCall, mHandle,
+                            mPhoneAccountHandle, mGatewayInfo, mSpeakerphoneOn, mVideoState,
+                            mShouldCancelCall, mUiAction);
                 }
             }
         }.prepare());
     }
 
-    /*
+    /**
      * The entry to perform call redirection of the call from (@link CallsManager)
      */
     public void performCallRedirection() {
-        performUserDefinedCallRedirection();
+        // If the Gateway Info is set with intent, do not perform call redirection.
+        if (mGatewayInfo != null) {
+            mCallsManager.onCallRedirectionComplete(mCall, mHandle, mPhoneAccountHandle,
+                    mGatewayInfo, mSpeakerphoneOn, mVideoState, mShouldCancelCall, mUiAction);
+        } else {
+            mCallRedirectionProcessorHelper.storePostDialDigits(mHandle);
+            performUserDefinedCallRedirection();
+        }
     }
 
     private void performUserDefinedCallRedirection() {
         Log.d(this, "performUserDefinedCallRedirection");
-        ComponentName componentName = getUserDefinedCallRedirectionService(mContext);
-        if (componentName != null && canBindToCallRedirectionService(mContext, componentName)) {
+        ComponentName componentName =
+                mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService();
+        if (componentName != null) {
             mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_USER_DEFINED);
             mAttempt.process();
             mIsUserDefinedRedirectionPending = true;
@@ -306,9 +344,10 @@
 
     private void performCarrierCallRedirection() {
         Log.d(this, "performCarrierCallRedirection");
-        ComponentName componentName = getCarrierCallRedirectionService(
-            mContext, mPhoneAccountHandle);
-        if (componentName != null && canBindToCallRedirectionService(mContext, componentName)) {
+        ComponentName componentName =
+                mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
+                        mPhoneAccountHandle);
+        if (componentName != null) {
             mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_CARRIER);
             mAttempt.process();
             mIsCarrierRedirectionPending = true;
@@ -316,7 +355,9 @@
         } else {
             Log.i(this, "There are no carrier call redirection services installed on this"
                     + " device.");
-            // TODO return to CallsManager.onCallRedirectionComplete
+            mCallsManager.onCallRedirectionComplete(mCall, mHandle,
+                    mPhoneAccountHandle, mGatewayInfo, mSpeakerphoneOn, mVideoState,
+                    mShouldCancelCall, mUiAction);
         }
     }
 
@@ -333,77 +374,49 @@
                         serviceType.equals(SERVICE_TYPE_USER_DEFINED) ?
                                 mIsUserDefinedRedirectionPending : mIsCarrierRedirectionPending;
                 if (isCurrentRedirectionPending) {
-                    Log.i(CallRedirectionProcessor.this,
-                            serviceType + "call redirection has timed out.");
+                    Log.i(this, serviceType + " call redirection has timed out.");
                     Log.addEvent(mCall, serviceType.equals(SERVICE_TYPE_USER_DEFINED)
                             ? LogUtils.Events.REDIRECTION_TIMED_OUT_USER
                             : LogUtils.Events.REDIRECTION_TIMED_OUT_CARRIER);
+                    if (serviceType.equals(SERVICE_TYPE_USER_DEFINED)) {
+                        mUiAction = UI_TYPE_USER_DEFINED_TIMEOUT;
+                        mShouldCancelCall = true;
+                    }
                     onCallRedirectionComplete(mCall);
                 }
             }
         }.prepare(), timeout);
     }
 
-    private ComponentName getUserDefinedCallRedirectionService(Context context) {
-        // TODO get service component name from settings default value:
-        // android.provider.Settings#CALL_REDIRECTION_DEFAULT_APPLICATION
-        return null;
-    }
-
-    private ComponentName getCarrierCallRedirectionService(Context context, PhoneAccountHandle
-            targetPhoneAccountHandle) {
-        CarrierConfigManager configManager = (CarrierConfigManager)
-                context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager == null) {
-            Log.i(this, "Cannot get CarrierConfigManager.");
-            return null;
-        }
-        PersistableBundle pb = configManager.getConfigForSubId(mPhoneAccountRegistrar
-                .getSubscriptionIdForPhoneAccount(targetPhoneAccountHandle));
-        if (pb == null) {
-            Log.i(this, "Cannot get PersistableBundle.");
-            return null;
-        }
-        String componentNameString = pb.getString(
-            CarrierConfigManager.KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING);
-        return new ComponentName(context, componentNameString);
-    }
-
-    private boolean canBindToCallRedirectionService(Context context, ComponentName componentName) {
-        Intent intent = new Intent(CallRedirectionService.SERVICE_INTERFACE);
-        intent.setComponent(componentName);
-        List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
-                intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
-        if (entries.isEmpty()) {
-            Log.i(this, "There are no call redirection services installed on this device.");
-            return false;
-        } else if (entries.size() != 1) {
-            Log.i(this, "There are multiple call redirection services installed on this device.");
-            return false;
-        } else {
-            ResolveInfo entry = entries.get(0);
-            if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
-                    Manifest.permission.BIND_CALL_REDIRECTION_SERVICE)) {
-                Log.w(this, "CallRedirectionService must require BIND_CALL_REDIRECTION_SERVICE"
-                        + " permission: " + entry.serviceInfo.packageName);
-                return false;
-            }
-            AppOpsManager appOps = (AppOpsManager) context.getSystemService(
-                Context.APP_OPS_SERVICE);
-            if (appOps.noteOp(AppOpsManager.OP_PROCESS_OUTGOING_CALLS, Binder.getCallingUid(),
-                    entry.serviceInfo.packageName) != AppOpsManager.MODE_ALLOWED) {
-                Log.w(this, "App Ops does not allow " + entry.serviceInfo.packageName);
-                return false;
-            }
-        }
-        return true;
+    /**
+     * Checks if Telecom can make call redirection with any available call redirection service.
+     *
+     * @return {@code true} if it can; {@code false} otherwise.
+     */
+    public boolean canMakeCallRedirectionWithService() {
+        boolean canMakeCallRedirectionWithService =
+                mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService() != null
+                        || mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
+                                mPhoneAccountHandle) != null;
+        Log.w(this, "Can make call redirection with any available service: "
+                + canMakeCallRedirectionWithService);
+        return canMakeCallRedirectionWithService;
     }
 
     /**
-     * Returns the handler for testing purposes.
+     * Returns the handler, for testing purposes.
      */
     @VisibleForTesting
     public Handler getHandler() {
         return mHandler;
     }
+
+    /**
+     * Set CallRedirectionProcessorHelper for testing purposes.
+     */
+    @VisibleForTesting
+    public void setCallRedirectionServiceHelper(
+            CallRedirectionProcessorHelper callRedirectionProcessorHelper) {
+        mCallRedirectionProcessorHelper = callRedirectionProcessorHelper;
+    }
 }
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
new file mode 100644
index 0000000..5e3f0da
--- /dev/null
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom.callredirection;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.PersistableBundle;
+import android.provider.Settings;
+import android.telecom.CallRedirectionService;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.PhoneAccountRegistrar;
+
+import java.util.List;
+
+public class CallRedirectionProcessorHelper {
+
+    private final Context mContext;
+    private final CallsManager mCallsManager;
+    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+    private String mOriginalPostDialDigits = null;
+
+    public CallRedirectionProcessorHelper(
+            Context context,
+            CallsManager callsManager,
+            PhoneAccountRegistrar phoneAccountRegistrar) {
+        mContext = context;
+        mCallsManager = callsManager;
+        mPhoneAccountRegistrar = phoneAccountRegistrar;
+    }
+
+    @VisibleForTesting
+    // TODO integarte with RoleManager functions
+    public ComponentName getUserDefinedCallRedirectionService() {
+        String componentNameString = Settings.Secure.getStringForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.CALL_REDIRECTION_DEFAULT_APPLICATION,
+                mCallsManager.getCurrentUserHandle().getIdentifier());
+        if (TextUtils.isEmpty(componentNameString)) {
+            Log.i(this, "Default user-defined call redirection is empty. Not performing call"
+                    + " redirection.");
+            return null;
+        }
+        return getComponentName(componentNameString,
+                CallRedirectionProcessor.SERVICE_TYPE_USER_DEFINED);
+    }
+
+    @VisibleForTesting
+    public ComponentName getCarrierCallRedirectionService(
+            PhoneAccountHandle targetPhoneAccountHandle) {
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager == null) {
+            Log.i(this, "Cannot get CarrierConfigManager.");
+            return null;
+        }
+        PersistableBundle pb = configManager.getConfigForSubId(mPhoneAccountRegistrar
+                .getSubscriptionIdForPhoneAccount(targetPhoneAccountHandle));
+        if (pb == null) {
+            Log.i(this, "Cannot get PersistableBundle.");
+            return null;
+        }
+        String componentNameString = pb.getString(
+                CarrierConfigManager.KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING);
+        if (componentNameString == null) {
+            Log.i(this, "Cannot get carrier componentNameString.");
+            return null;
+        }
+        return getComponentName(componentNameString,
+                CallRedirectionProcessor.SERVICE_TYPE_CARRIER);
+    }
+
+    protected ComponentName getComponentName(String componentNameString, String serviceType) {
+        ComponentName componentName = ComponentName.unflattenFromString(componentNameString);
+        if (componentName == null) {
+            Log.w(this, "ComponentName is null from string: " + componentNameString);
+            return null;
+        }
+        Intent intent = new Intent(CallRedirectionService.SERVICE_INTERFACE);
+        intent.setComponent(componentName);
+        List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
+                intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
+        if (entries.isEmpty()) {
+            Log.i(this, "There are no " + serviceType + " call redirection services installed" +
+                    " on this device.");
+            return null;
+        } else if (entries.size() != 1) {
+            Log.i(this, "There are multiple " + serviceType + " call redirection services" +
+                    " installed on this device.");
+            return null;
+        }
+        ResolveInfo entry = entries.get(0);
+        if (entry.serviceInfo == null) {
+            Log.w(this, "The " + serviceType + " call redirection service has invalid" +
+                    " service info");
+            return null;
+        }
+        if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
+                Manifest.permission.BIND_CALL_REDIRECTION_SERVICE)) {
+            Log.w(this, "CallRedirectionService must require BIND_CALL_REDIRECTION_SERVICE"
+                    + " permission: " + entry.serviceInfo.packageName);
+            return null;
+        }
+        AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
+                Context.APP_OPS_SERVICE);
+        if (appOps.noteOpNoThrow(AppOpsManager.OP_PROCESS_OUTGOING_CALLS, Binder.getCallingUid(),
+                entry.serviceInfo.packageName) != AppOpsManager.MODE_ALLOWED) {
+            Log.w(this, "App Ops does not allow " + entry.serviceInfo.packageName);
+            return null;
+        }
+        return componentName;
+    }
+
+    /**
+     * Format Number to E164, and remove post dial digits.
+     */
+    protected Uri formatNumberForRedirection(Uri handle) {
+        return removePostDialDigits(formatNumberToE164(handle));
+    }
+
+    protected Uri processNumberWhenRedirectionComplete(Uri handle) {
+        return appendStoredPostDialDigits(formatNumberForRedirection(handle));
+    }
+
+    protected void storePostDialDigits(Uri handle) {
+        String number = handle.getSchemeSpecificPart();
+        mOriginalPostDialDigits += PhoneNumberUtils.extractPostDialPortion(number);
+        Log.i(this, "storePostDialDigits, stored post dial digits: "
+                + Log.pii(mOriginalPostDialDigits));
+    }
+
+    protected Uri appendStoredPostDialDigits(Uri handle) {
+        String number = handle.getSchemeSpecificPart();
+        number += mOriginalPostDialDigits;
+        Log.i(this, "appendStoredPostDialDigits, appended number: " + Log.pii(number));
+        return Uri.fromParts(handle.getScheme(), number, null);
+    }
+
+    protected Uri formatNumberToE164(Uri handle) {
+        String number = handle.getSchemeSpecificPart();
+
+        // Format number to E164
+        TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        Log.i(this, "formatNumberToE164, original number: " + Log.pii(number));
+        number = PhoneNumberUtils.formatNumberToE164(number, tm.getNetworkCountryIso());
+        Log.i(this, "formatNumberToE164, formatted E164 number: " + Log.pii(number));
+        // if there is a problem with parsing the phone number, formatNumberToE164 will return null;
+        // and should just use the original number in that case.
+        if (number == null) {
+            return handle;
+        } else {
+            return Uri.fromParts(handle.getScheme(), number, null);
+        }
+    }
+
+    protected Uri removePostDialDigits(Uri handle) {
+        String number = handle.getSchemeSpecificPart();
+
+        // Extract the post dial portion
+        number = PhoneNumberUtils.extractNetworkPortionAlt(number);
+        Log.i(this, "removePostDialDigits, number after being extracted post dial digits: "
+                + Log.pii(number));
+        // if there is a problem with parsing the phone number, removePostDialDigits will return
+        // null; and should just use the original number in that case.
+        if (number == null) {
+            return handle;
+        } else {
+            return Uri.fromParts(handle.getScheme(), number, null);
+        }
+    }
+
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
new file mode 100644
index 0000000..fa78383
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom.tests;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.telecom.GatewayInfo;
+import android.telecom.PhoneAccountHandle;
+import com.android.internal.telecom.ICallRedirectionService;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.SystemStateHelper;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+
+import com.android.server.telecom.callredirection.CallRedirectionProcessor;
+import com.android.server.telecom.callredirection.CallRedirectionProcessorHelper;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import org.junit.Before;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(JUnit4.class)
+public class CallRedirectionProcessorTest extends TelecomTestCase {
+    @Mock private Context mContext;
+    @Mock private CallsManager mCallsManager;
+    @Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
+    @Mock private PhoneAccountHandle mPhoneAccountHandle;
+    private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+    @Mock private Call mCall;
+
+    @Mock private PackageManager mPackageManager;
+    @Mock private IBinder mBinder;
+    @Mock private ICallRedirectionService mCallRedirectionService;
+
+    @Mock private SystemStateHelper mSystemStateHelper;
+    @Mock private CallRedirectionProcessorHelper mCallRedirectionProcessorHelper;
+
+    @Mock private Uri mHandle;
+    @Mock private GatewayInfo mGatewayInfo;
+    @Mock private UserHandle mUserHandle;
+    @Mock private ContentResolver mContentResolver;
+
+    @Mock private Timeouts.Adapter mTimeoutsAdapter;
+
+    private static final String USER_DEFINED_PKG_NAME = "user_defined_pkg";
+    private static final String USER_DEFINED_CLS_NAME = "user_defined_cls";
+    private static final String CARRIER_PKG_NAME = "carrier_pkg";
+    private static final String CARRIER_CLS_NAME = "carrier_cls";
+
+    private static final long HANDLER_TIMEOUT_DELAY = 5000;
+    private static final long USER_DEFINED_SHORT_TIMEOUT_MS = 1200;
+    private static final long CARRIER_SHORT_TIMEOUT_MS = 400;
+    private static final long CODE_EXECUTION_DELAY = 500;
+
+    // TODO integerate with a test user-defined service
+    private static final ComponentName USER_DEFINED_SERVICE_TEST_COMPONENT_NAME =
+            new ComponentName(USER_DEFINED_PKG_NAME, USER_DEFINED_CLS_NAME);
+    // TODO integerate with a test carrier service
+    private static final ComponentName CARRIER_SERVICE_TEST_COMPONENT_NAME =
+            new ComponentName(CARRIER_PKG_NAME, CARRIER_CLS_NAME);
+
+    private static final boolean SPEAKER_PHONE_ON = true;
+    private static final int VIDEO_STATE = 0;
+
+    private CallRedirectionProcessor mProcessor;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        when(mCall.getTargetPhoneAccount()).thenReturn(mPhoneAccountHandle);
+        when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        doReturn(mCallRedirectionService).when(mBinder).queryLocalInterface(anyString());
+        when(mCallsManager.getSystemStateHelper()).thenReturn(mSystemStateHelper);
+        when(mCallsManager.getTimeoutsAdapter()).thenReturn(mTimeoutsAdapter);
+        when(mTimeoutsAdapter.getUserDefinedCallRedirectionTimeoutMillis(mContentResolver))
+                .thenReturn(USER_DEFINED_SHORT_TIMEOUT_MS);
+        when(mTimeoutsAdapter.getCarrierCallRedirectionTimeoutMillis(mContentResolver))
+                .thenReturn(CARRIER_SHORT_TIMEOUT_MS);
+        when(mCallsManager.getLock()).thenReturn(mLock);
+        when(mCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
+                anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+    }
+
+    private void setIsInCarMode(boolean isInCarMode) {
+        when(mSystemStateHelper.isCarMode()).thenReturn(isInCarMode);
+    }
+
+    private void enableUserDefinedCallRedirectionService() {
+        when(mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService()).thenReturn(
+                USER_DEFINED_SERVICE_TEST_COMPONENT_NAME);
+    }
+
+    private void enableCarrierCallRedirectionService() {
+        when(mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
+                any(PhoneAccountHandle.class))).thenReturn(CARRIER_SERVICE_TEST_COMPONENT_NAME);
+    }
+
+    private void disableUserDefinedCallRedirectionService() {
+        when(mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService()).thenReturn(
+                null);
+    }
+
+    private void disableCarrierCallRedirectionService() {
+        when(mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(any())).thenReturn(
+                null);
+    }
+
+    private void startProcessWithNoGateWayInfo() {
+        mProcessor = new CallRedirectionProcessor(mContext, mCallsManager, mCall, mHandle,
+                mPhoneAccountRegistrar, null, SPEAKER_PHONE_ON, VIDEO_STATE);
+        mProcessor.setCallRedirectionServiceHelper(mCallRedirectionProcessorHelper);
+    }
+
+    private void startProcessWithGateWayInfo() {
+        mProcessor = new CallRedirectionProcessor(mContext, mCallsManager, mCall, mHandle,
+                mPhoneAccountRegistrar, mGatewayInfo, SPEAKER_PHONE_ON, VIDEO_STATE);
+        mProcessor.setCallRedirectionServiceHelper(mCallRedirectionProcessorHelper);
+    }
+
+    @Test
+    public void testNoUserDefinedServiceNoCarrierSerivce() {
+        startProcessWithNoGateWayInfo();
+        disableUserDefinedCallRedirectionService();
+        disableCarrierCallRedirectionService();
+        mProcessor.performCallRedirection();
+        verify(mContext, times(0)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), eq(mHandle),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+    }
+
+    @Test
+    public void testCarrierServiceTimeoutNoUserDefinedService() throws Exception {
+        startProcessWithNoGateWayInfo();
+        // To make sure tests are not flaky, clean all the previous handler messages
+        waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
+        disableUserDefinedCallRedirectionService();
+        enableCarrierCallRedirectionService();
+        mProcessor.performCallRedirection();
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+        waitForHandlerActionDelayed(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY,
+                CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+        verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+    }
+
+    @Test
+    public void testUserDefinedServiceTimeoutNoCarrierService() throws Exception {
+        startProcessWithNoGateWayInfo();
+        // To make sure tests are not flaky, clean all the previous handler messages
+        waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
+        enableUserDefinedCallRedirectionService();
+        disableCarrierCallRedirectionService();
+        mProcessor.performCallRedirection();
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+        // Test it is waiting for a User-defined timeout, not a Carrier timeout
+        Thread.sleep(CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+        verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+        // Wait for the rest of user-defined timeout time.
+        waitForHandlerActionDelayed(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY,
+                USER_DEFINED_SHORT_TIMEOUT_MS - CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+        verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(true), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+    }
+
+    @Test
+    public void testUserDefinedServiceTimeoutAndCarrierServiceTimeout() throws Exception {
+        startProcessWithNoGateWayInfo();
+        // To make sure tests are not flaky, clean all the previous handler messages
+        waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
+        enableUserDefinedCallRedirectionService();
+        enableCarrierCallRedirectionService();
+        mProcessor.performCallRedirection();
+
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+        // Test it is waiting for a User-defined timeout, not a Carrier timeout
+        Thread.sleep(CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+        // Wait for the rest of user-defined timeout time.
+        waitForHandlerActionDelayed(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY,
+                USER_DEFINED_SHORT_TIMEOUT_MS - CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+        verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(true), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+        // Wait for another carrier timeout time, but should not expect any carrier service request
+        // is triggered.
+        Thread.sleep(CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(true), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+    }
+
+    @Test
+    public void testProcessGatewayCall() {
+        startProcessWithGateWayInfo();
+        enableUserDefinedCallRedirectionService();
+        enableCarrierCallRedirectionService();
+        mProcessor.performCallRedirection();
+        verify(mContext, times(0)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), eq(mHandle),
+                eq(mPhoneAccountHandle), eq(mGatewayInfo), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
index 4c0e76a..cfbcfcf 100644
--- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -28,6 +28,7 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.telecom.GatewayInfo;
+import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.DisconnectCause;
@@ -36,8 +37,10 @@
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.NewOutgoingCallIntentBroadcaster;
+import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
+import com.android.server.telecom.SystemStateHelper;
 import com.android.server.telecom.TelecomSystem;
 
 import org.junit.Before;
@@ -78,6 +81,9 @@
 
     @Mock private CallsManager mCallsManager;
     @Mock private Call mCall;
+    @Mock private SystemStateHelper mSystemStateHelper;
+    @Mock private UserHandle mUserHandle;
+    @Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
 
     private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapterSpy;
 
@@ -89,6 +95,12 @@
         mPhoneNumberUtilsAdapterSpy = spy(new PhoneNumberUtilsAdapterImpl());
         when(mCall.getInitiatingUser()).thenReturn(UserHandle.CURRENT);
         when(mCallsManager.getLock()).thenReturn(new TelecomSystem.SyncRoot() { });
+        when(mCallsManager.getSystemStateHelper()).thenReturn(mSystemStateHelper);
+        when(mCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mCallsManager.getPhoneAccountRegistrar()).thenReturn(mPhoneAccountRegistrar);
+        when(mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
+                any(PhoneAccountHandle.class))).thenReturn(-1);
+        when(mSystemStateHelper.isCarMode()).thenReturn(false);
     }
 
     @SmallTest