Merge "Add updates for the BT route for the UI."
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index dfd7b2c..cc80db2 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -132,6 +132,7 @@
         void onConnectionManagerPhoneAccountChanged(Call call);
         void onPhoneAccountChanged(Call call);
         void onConferenceableCallsChanged(Call call);
+        void onConferenceStateChanged(Call call, boolean isConference);
         boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout);
         void onHoldToneRequested(Call call);
         void onConnectionEvent(Call call, String event, Bundle extras);
@@ -201,6 +202,8 @@
         @Override
         public void onConferenceableCallsChanged(Call call) {}
         @Override
+        public void onConferenceStateChanged(Call call, boolean isConference) {}
+        @Override
         public boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout) {
             return false;
         }
@@ -1472,7 +1475,6 @@
                         "capable when not supported by the phone account.");
                 connectionCapabilities = removeVideoCapabilities(connectionCapabilities);
             }
-
             int previousCapabilities = mConnectionCapabilities;
             mConnectionCapabilities = connectionCapabilities;
             for (Listener l : mListeners) {
@@ -3108,6 +3110,20 @@
     }
 
     /**
+     * Sets whether this {@link Call} is a conference or not.
+     * @param isConference
+     */
+    public void setConferenceState(boolean isConference) {
+        mIsConference = isConference;
+        Log.addEvent(this, LogUtils.Events.CONF_STATE_CHANGED, "isConference=" + isConference);
+        // Ultimately CallsManager needs to know so it can update the "add call" state and inform
+        // the UI to update itself.
+        for (Listener l : mListeners) {
+            l.onConferenceStateChanged(this, isConference);
+        }
+    }
+
+    /**
      * Sets the video history based on the state and state transitions of the call. Always add the
      * current video state to the video state history during a call transition except for the
      * transitions DIALING->ACTIVE and RINGING->ANSWERED. In these cases, clear the history. If a
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index cda6054..e4535d9 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -141,6 +141,7 @@
         void onExternalCallChanged(Call call, boolean isExternalCall);
         void onDisconnectedTonePlaying(boolean isTonePlaying);
         void onConnectionTimeChanged(Call call);
+        void onConferenceStateChanged(Call call, boolean isConference);
     }
 
     /** Interface used to define the action which is executed delay under some condition. */
@@ -299,6 +300,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 +438,7 @@
         mMissedCallNotifier = missedCallNotifier;
         StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
         mWiredHeadsetManager = wiredHeadsetManager;
+        mSystemStateHelper = systemStateHelper;
         mDefaultDialerCache = defaultDialerCache;
         mBluetoothRouteManager = bluetoothManager;
         mDockManager = new DockManager(context);
@@ -801,6 +804,15 @@
     }
 
     @Override
+    public void onConferenceStateChanged(Call call, boolean isConference) {
+        // Conference changed whether it is treated as a conference or not.
+        updateCanAddCall();
+        for (CallsManagerListener listener : mListeners) {
+            listener.onConferenceStateChanged(call, isConference);
+        }
+    }
+
+    @Override
     public void onIsVoipAudioModeChanged(Call call) {
         for (CallsManagerListener listener : mListeners) {
             listener.onIsVoipAudioModeChanged(call);
@@ -1606,6 +1618,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.
      *
@@ -2590,7 +2653,8 @@
      *
      * @return The {@link PhoneAccountRegistrar}.
      */
-    PhoneAccountRegistrar getPhoneAccountRegistrar() {
+    @VisibleForTesting
+    public PhoneAccountRegistrar getPhoneAccountRegistrar() {
         return mPhoneAccountRegistrar;
     }
 
@@ -3387,6 +3451,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/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index cdc0209..4fa2ee5 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -96,4 +96,8 @@
     @Override
     public void onConnectionTimeChanged(Call call) {
     }
+
+    @Override
+    public void onConferenceStateChanged(Call call, boolean isConference) {
+    }
 }
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 6c63ecb..90064cd 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -990,6 +990,27 @@
                 Log.endSession();
             }
         }
+
+        @Override
+        public void setConferenceState(String callId, boolean isConference,
+                Session.Info sessionInfo) throws RemoteException {
+            Log.startSession(sessionInfo, "CSW.sCS");
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setConferenceState(isConference);
+                    }
+                }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
+        }
     }
 
     private final Adapter mAdapter = new Adapter();
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index cf5bf89..142cf3a 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -972,6 +972,12 @@
         updateCall(call);
     }
 
+    @Override
+    public void onConferenceStateChanged(Call call, boolean isConference) {
+        Log.d(this, "onConferenceStateChanged %s ,isConf=%b", call, isConference);
+        updateCall(call);
+    }
+
     void bringToForeground(boolean showDialpad) {
         if (!mInCallServices.isEmpty()) {
             for (IInCallService inCallService : mInCallServices.values()) {
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index bf8dba7..71ed67f 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -95,6 +95,7 @@
         public static final String ADD_CHILD = "ADD_CHILD";
         public static final String REMOVE_CHILD = "REMOVE_CHILD";
         public static final String SET_PARENT = "SET_PARENT";
+        public static final String CONF_STATE_CHANGED = "CONF_STATE_CHANGED";
         public static final String MUTE = "MUTE";
         public static final String UNMUTE = "UNMUTE";
         public static final String AUDIO_ROUTE = "AUDIO_ROUTE";
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/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 196b8ad..9010913 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -332,7 +332,7 @@
                 int subId = getSubscriptionIdForPhoneAccount(accountHandle);
                 mSubscriptionManager.setDefaultVoiceSubId(subId);
             }
-
+            Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s", accountHandle);
             mState.defaultOutgoingAccountHandles
                     .put(userHandle, new DefaultPhoneAccountHandle(userHandle, accountHandle,
                             account.getGroupId()));
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index f57a0c6..f472421 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -137,10 +137,14 @@
         }
 
         @Override
-        public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
+        public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(String callingPackage) {
             synchronized (mLock) {
                 try {
                     Log.startSession("TSI.gUSOPA");
+                    if (!isDialerOrPrivileged(callingPackage, "getDefaultOutgoingPhoneAccount")) {
+                        throw new SecurityException("Only the default dialer, or caller with "
+                                + "READ_PRIVILEGED_PHONE_STATE can call this method.");
+                    }
                     final UserHandle callingUserHandle = Binder.getCallingUserHandle();
                     return mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount(
                             callingUserHandle);
@@ -1909,6 +1913,19 @@
         }
     }
 
+    private boolean isDialerOrPrivileged(String callingPackage, String message) {
+        // The system/default dialer can always read phone state - so that emergency calls will
+        // still work.
+        if (isPrivilegedDialerCalling(callingPackage)) {
+            return true;
+        }
+
+        mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, message);
+        // SKIP checking run-time OP_READ_PHONE_STATE since caller or self has PRIVILEGED
+        // permission
+        return true;
+    }
+
     private boolean isSelfManagedConnectionService(PhoneAccountHandle phoneAccountHandle) {
         if (phoneAccountHandle != null) {
                 PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
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
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index c11811d..b779ce2 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -22,6 +22,8 @@
 import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 
+import android.annotation.MainThread;
+import android.annotation.WorkerThread;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
@@ -271,7 +273,8 @@
                 makeMultiUserPhoneAccount(TEL_PA_HANDLE_16).build());
 
         PhoneAccountHandle returnedHandle
-                = mTSIBinder.getUserSelectedOutgoingPhoneAccount();
+                = mTSIBinder.getUserSelectedOutgoingPhoneAccount(
+                        TEL_PA_HANDLE_16.getComponentName().getPackageName());
         assertEquals(TEL_PA_HANDLE_16, returnedHandle);
     }