Merge "Change hasVoicemailNumber to getVoicemailNumber"
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 4b1cef5..7edda41 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -42,7 +42,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.IVideoProvider;
 import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.CallerInfoAsyncQuery;
 import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
 import com.android.internal.telephony.SmsApplication;
 import com.android.server.telecom.ContactsAsyncHelper.OnImageLoadCompleteListener;
@@ -152,25 +151,29 @@
         public void onCallSubstateChanged(Call call) {}
     }
 
-    private static final OnQueryCompleteListener sCallerInfoQueryListener =
+    private final OnQueryCompleteListener mCallerInfoQueryListener =
             new OnQueryCompleteListener() {
                 /** ${inheritDoc} */
                 @Override
                 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
-                    if (cookie != null) {
-                        ((Call) cookie).setCallerInfo(callerInfo, token);
+                    synchronized (mLock) {
+                        if (cookie != null) {
+                            ((Call) cookie).setCallerInfo(callerInfo, token);
+                        }
                     }
                 }
             };
 
-    private static final OnImageLoadCompleteListener sPhotoLoadListener =
+    private final OnImageLoadCompleteListener mPhotoLoadListener =
             new OnImageLoadCompleteListener() {
                 /** ${inheritDoc} */
                 @Override
                 public void onImageLoadComplete(
                         int token, Drawable photo, Bitmap photoIcon, Object cookie) {
-                    if (cookie != null) {
-                        ((Call) cookie).setPhoto(photo, photoIcon, token);
+                    synchronized (mLock) {
+                        if (cookie != null) {
+                            ((Call) cookie).setPhoto(photo, photoIcon, token);
+                        }
                     }
                 }
             };
@@ -310,6 +313,7 @@
     private final CallsManager mCallsManager;
     private final TelecomSystem.SyncRoot mLock;
     private int mCallSubstate;
+    private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
 
     private boolean mWasConferencePreviouslyMerged = false;
 
@@ -341,6 +345,7 @@
             TelecomSystem.SyncRoot lock,
             ConnectionServiceRepository repository,
             ContactsAsyncHelper contactsAsyncHelper,
+            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
             Uri handle,
             GatewayInfo gatewayInfo,
             PhoneAccountHandle connectionManagerPhoneAccountHandle,
@@ -353,6 +358,7 @@
         mLock = lock;
         mRepository = repository;
         mContactsAsyncHelper = contactsAsyncHelper;
+        mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
         setHandle(handle);
         setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
         mGatewayInfo = gatewayInfo;
@@ -384,6 +390,7 @@
             TelecomSystem.SyncRoot lock,
             ConnectionServiceRepository repository,
             ContactsAsyncHelper contactsAsyncHelper,
+            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
             Uri handle,
             GatewayInfo gatewayInfo,
             PhoneAccountHandle connectionManagerPhoneAccountHandle,
@@ -391,7 +398,8 @@
             boolean isIncoming,
             boolean isConference,
             long connectTimeMillis) {
-        this(context, callsManager, lock, repository, contactsAsyncHelper, handle, gatewayInfo,
+        this(context, callsManager, lock, repository, contactsAsyncHelper,
+                callerInfoAsyncQueryFactory, handle, gatewayInfo,
                 connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, isIncoming,
                 isConference);
 
@@ -416,18 +424,48 @@
             component = mConnectionService.getComponentName().flattenToShortString();
         }
 
-        return String.format(Locale.US, "[%s, %s, %s, %s, %d, childs(%d), has_parent(%b), [%s], %d]",
+
+
+        return String.format(Locale.US, "[%s, %s, %s, %s, %s, childs(%d), has_parent(%b), [%s], %d]",
                 System.identityHashCode(this),
                 CallState.toString(mState),
                 component,
                 Log.piiHandle(mHandle),
-                getVideoState(),
+                getVideoStateDescription(getVideoState()),
                 getChildCalls().size(),
                 getParentCall() != null,
                 Connection.capabilitiesToString(getConnectionCapabilities()),
                 getCallSubstate());
     }
 
+    /**
+     * Builds a debug-friendly description string for a video state.
+     * <p>
+     * A = audio active, T = video transmission active, R = video reception active, P = video
+     * paused.
+     *
+     * @param videoState The video state.
+     * @return A string indicating which bits are set in the video state.
+     */
+    private String getVideoStateDescription(int videoState) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("A");
+
+        if (VideoProfile.VideoState.isTransmissionEnabled(videoState)) {
+            sb.append("T");
+        }
+
+        if (VideoProfile.VideoState.isReceptionEnabled(videoState)) {
+            sb.append("R");
+        }
+
+        if (VideoProfile.VideoState.isPaused(videoState)) {
+            sb.append("P");
+        }
+
+        return sb.toString();
+    }
+
     int getState() {
         return mState;
     }
@@ -1269,11 +1307,11 @@
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    CallerInfoAsyncQuery.startQuery(
+                    mCallerInfoAsyncQueryFactory.startQuery(
                             mQueryToken,
                             mContext,
                             number,
-                            sCallerInfoQueryListener,
+                            mCallerInfoQueryListener,
                             Call.this);
                 }
             });
@@ -1302,7 +1340,7 @@
                         token,
                         mContext,
                         mCallerInfo.contactDisplayPhotoUri,
-                        sPhotoLoadListener,
+                        mPhotoLoadListener,
                         this);
                 // Do not call onCallerInfoChanged yet in this case.  We call it in setPhoto().
             } else {
diff --git a/src/com/android/server/telecom/CallerInfoAsyncQueryFactory.java b/src/com/android/server/telecom/CallerInfoAsyncQueryFactory.java
new file mode 100644
index 0000000..149470f
--- /dev/null
+++ b/src/com/android/server/telecom/CallerInfoAsyncQueryFactory.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 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;
+
+import com.android.internal.telephony.CallerInfoAsyncQuery;
+
+import android.content.Context;
+
+public interface CallerInfoAsyncQueryFactory {
+    CallerInfoAsyncQuery startQuery(int token, Context context, String number,
+            CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie);
+}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 371660a..5fd4fca 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -129,6 +129,7 @@
     private final Context mContext;
     private final TelecomSystem.SyncRoot mLock;
     private final ContactsAsyncHelper mContactsAsyncHelper;
+    private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final MissedCallNotifier mMissedCallNotifier;
     private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
@@ -153,6 +154,7 @@
             Context context,
             TelecomSystem.SyncRoot lock,
             ContactsAsyncHelper contactsAsyncHelper,
+            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
             MissedCallNotifier missedCallNotifier,
             PhoneAccountRegistrar phoneAccountRegistrar,
             HeadsetMediaButtonFactory headsetMediaButtonFactory,
@@ -161,6 +163,7 @@
         mContext = context;
         mLock = lock;
         mContactsAsyncHelper = contactsAsyncHelper;
+        mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mMissedCallNotifier = missedCallNotifier;
         StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
@@ -168,7 +171,7 @@
         mDockManager = new DockManager(context);
         mCallAudioManager = new CallAudioManager(
                 context, statusBarNotifier, mWiredHeadsetManager, mDockManager, this);
-        InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager);
+        InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager, lock);
         mRinger = new Ringer(mCallAudioManager, this, playerFactory, context);
         mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this);
         mTtyManager = new TtyManager(context, mWiredHeadsetManager);
@@ -194,7 +197,8 @@
         mListeners.add(mHeadsetMediaButton);
         mListeners.add(mProximitySensorManager);
 
-        mMissedCallNotifier.updateOnStartup(mLock, this, mContactsAsyncHelper);
+        mMissedCallNotifier.updateOnStartup(
+                mLock, this, mContactsAsyncHelper, mCallerInfoAsyncQueryFactory);
     }
 
     public void setRespondViaSmsManager(RespondViaSmsManager respondViaSmsManager) {
@@ -446,6 +450,7 @@
                 mLock,
                 mConnectionServiceRepository,
                 mContactsAsyncHelper,
+                mCallerInfoAsyncQueryFactory,
                 handle,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
@@ -468,6 +473,7 @@
                 mLock,
                 mConnectionServiceRepository,
                 mContactsAsyncHelper,
+                mCallerInfoAsyncQueryFactory,
                 handle,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
@@ -507,6 +513,7 @@
                 mLock,
                 mConnectionServiceRepository,
                 mContactsAsyncHelper,
+                mCallerInfoAsyncQueryFactory,
                 handle,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
@@ -1111,6 +1118,7 @@
                 mLock,
                 mConnectionServiceRepository,
                 mContactsAsyncHelper,
+                mCallerInfoAsyncQueryFactory,
                 null /* handle */,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
@@ -1462,6 +1470,7 @@
                 mLock,
                 mConnectionServiceRepository,
                 mContactsAsyncHelper,
+                mCallerInfoAsyncQueryFactory,
                 connection.getHandle() /* handle */,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
diff --git a/src/com/android/server/telecom/ContactsAsyncHelper.java b/src/com/android/server/telecom/ContactsAsyncHelper.java
index ef7ea4c..44fa654 100644
--- a/src/com/android/server/telecom/ContactsAsyncHelper.java
+++ b/src/com/android/server/telecom/ContactsAsyncHelper.java
@@ -127,12 +127,12 @@
                             }
                         }
                     }
-                    synchronized (mLock) {
-                        Log.d(this, "Notifying listener: " + args.listener.toString() +
-                                " image: " + args.displayPhotoUri + " completed");
-                        args.listener.onImageLoadComplete(msg.what, args.photo, args.photoIcon,
+
+                    // Listener will synchronize as needed
+                    Log.d(this, "Notifying listener: " + args.listener.toString() +
+                            " image: " + args.displayPhotoUri + " completed");
+                    args.listener.onImageLoadComplete(msg.what, args.photo, args.photoIcon,
                                 args.cookie);
-                    }
                     break;
                 default:
                     break;
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 4e958ad..4c44bd7 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -624,7 +624,10 @@
         android.telecom.Call.Details.CAPABILITY_SHOW_CALLBACK_NUMBER,
 
         Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
-        android.telecom.Call.Details.CAPABILITY_CAN_UPGRADE_TO_VIDEO
+        android.telecom.Call.Details.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
+
+        Connection.CAPABILITY_CAN_PAUSE_VIDEO,
+        android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO
     };
 
     private static int convertConnectionToCallCapabilities(int connectionCapabilities) {
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 3b4380e..f51a639 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -33,13 +33,15 @@
      */
     public static class Factory {
         private final CallAudioManager mCallAudioManager;
+        private final TelecomSystem.SyncRoot mLock;
 
-        Factory(CallAudioManager callAudioManager) {
+        Factory(CallAudioManager callAudioManager, TelecomSystem.SyncRoot lock) {
             mCallAudioManager = callAudioManager;
+            mLock = lock;
         }
 
         InCallTonePlayer createPlayer(int tone) {
-            return new InCallTonePlayer(tone, mCallAudioManager);
+            return new InCallTonePlayer(tone, mCallAudioManager, mLock);
         }
     }
 
@@ -89,15 +91,22 @@
     /** Current state of the tone player. */
     private int mState;
 
+    /** Telecom lock object. */
+    private final TelecomSystem.SyncRoot mLock;
+
     /**
      * Initializes the tone player. Private; use the {@link Factory} to create tone players.
      *
      * @param toneId ID of the tone to play, see TONE_* constants.
      */
-    private InCallTonePlayer(int toneId, CallAudioManager callAudioManager) {
+    private InCallTonePlayer(
+            int toneId,
+            CallAudioManager callAudioManager,
+            TelecomSystem.SyncRoot lock) {
         mState = STATE_OFF;
         mToneId = toneId;
         mCallAudioManager = callAudioManager;
+        mLock = lock;
     }
 
     /** {@inheritDoc} */
@@ -247,10 +256,12 @@
         // Release focus on the main thread.
         mMainThreadHandler.post(new Runnable() {
             @Override public void run() {
-                if (sTonesPlaying == 0) {
-                    Log.wtf(this, "Over-releasing focus for tone player.");
-                } else if (--sTonesPlaying == 0) {
-                    mCallAudioManager.setIsTonePlaying(false);
+                synchronized (mLock) {
+                    if (sTonesPlaying == 0) {
+                        Log.wtf(this, "Over-releasing focus for tone player.");
+                    } else if (--sTonesPlaying == 0) {
+                        mCallAudioManager.setIsTonePlaying(false);
+                    }
                 }
             }
         });
diff --git a/src/com/android/server/telecom/MissedCallNotifier.java b/src/com/android/server/telecom/MissedCallNotifier.java
index 52055cf..5a88c34 100644
--- a/src/com/android/server/telecom/MissedCallNotifier.java
+++ b/src/com/android/server/telecom/MissedCallNotifier.java
@@ -28,5 +28,6 @@
     void updateOnStartup(
             TelecomSystem.SyncRoot lock,
             CallsManager callsManager,
-            ContactsAsyncHelper contactsAsyncHelper);
+            ContactsAsyncHelper contactsAsyncHelper,
+            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory);
 }
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index f780b6f..a264702 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -409,7 +409,7 @@
         @Override
         public void silenceRinger() {
             synchronized (mLock) {
-                enforceModifyPermission();
+                enforceModifyPermissionOrDefaultDialer();
                 mCallsManager.getRinger().silence();
             }
         }
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 22377c9..c3ab0dc 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -92,6 +92,7 @@
     public TelecomSystem(
             Context context,
             MissedCallNotifier missedCallNotifier,
+            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
             HeadsetMediaButtonFactory headsetMediaButtonFactory,
             ProximitySensorManagerFactory proximitySensorManagerFactory,
             InCallWakeLockControllerFactory inCallWakeLockControllerFactory) {
@@ -105,6 +106,7 @@
                 mContext,
                 mLock,
                 mContactsAsyncHelper,
+                callerInfoAsyncQueryFactory,
                 mMissedCallNotifier,
                 mPhoneAccountRegistrar,
                 headsetMediaButtonFactory,
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 49b4aa4..f7f4054 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -22,6 +22,8 @@
 import android.content.Intent;
 import android.os.IBinder;
 
+import com.android.internal.telephony.CallerInfoAsyncQuery;
+import com.android.server.telecom.CallerInfoAsyncQueryFactory;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.HeadsetMediaButton;
 import com.android.server.telecom.HeadsetMediaButtonFactory;
@@ -63,6 +65,17 @@
                     new TelecomSystem(
                             context,
                             new MissedCallNotifierImpl(context.getApplicationContext()),
+                            new CallerInfoAsyncQueryFactory() {
+                                @Override
+                                public CallerInfoAsyncQuery startQuery(int token, Context context,
+                                        String number,
+                                        CallerInfoAsyncQuery.OnQueryCompleteListener listener,
+                                        Object cookie) {
+                                    Log.i(TelecomSystem.getInstance(), "CallerInfoAsyncQuery.startQuery number=%s cookie=%s", number, cookie);
+                                    return CallerInfoAsyncQuery.startQuery(
+                                            token, context, number, listener, cookie);
+                                }
+                            },
                             new HeadsetMediaButtonFactory() {
                                 @Override
                                 public HeadsetMediaButton create(Context context,
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index d7e9759..11fca20 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom.ui;
 
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallerInfoAsyncQueryFactory;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.CallsManagerListenerBase;
 import com.android.server.telecom.Constants;
@@ -312,7 +313,8 @@
     public void updateOnStartup(
             final TelecomSystem.SyncRoot lock,
             final CallsManager callsManager,
-            final ContactsAsyncHelper contactsAsyncHelper) {
+            final ContactsAsyncHelper contactsAsyncHelper,
+            final CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory) {
         Log.d(this, "updateOnStartup()...");
 
         // instantiate query handler
@@ -343,8 +345,8 @@
 
                                 // Convert the data to a call object
                                 Call call = new Call(mContext, callsManager, lock,
-                                        null, contactsAsyncHelper, null, null, null, null, true,
-                                        false);
+                                        null, contactsAsyncHelper, callerInfoAsyncQueryFactory,
+                                        null, null, null, null, true, false);
                                 call.setDisconnectCause(
                                         new DisconnectCause(DisconnectCause.MISSED));
                                 call.setState(CallState.DISCONNECTED);
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 747d377..1b296d4 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -66,6 +66,15 @@
                 <data android:scheme="tel" />
                 <data android:scheme="sip" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="android.telecom.testapps.ACTION_HANGUP_CALLS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.telecom.testapps.ACTION_SEND_UPGRADE_REQUEST" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="int" />
+            </intent-filter>
         </activity>
 
         <receiver android:name="com.android.server.telecom.testapps.CallNotificationReceiver"
diff --git a/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java b/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
index 0589a8e..36a3493 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
@@ -112,4 +112,11 @@
         LocalBroadcastManager.getInstance(context).sendBroadcast(
                 new Intent(TestCallActivity.ACTION_HANGUP_CALLS));
     }
+
+    public static void sendUpgradeRequest(Context context, Uri data) {
+        Log.i(TAG, "Sending upgrade request of type: " + data);
+        final Intent intent = new Intent(TestCallActivity.ACTION_SEND_UPGRADE_REQUEST);
+        intent.setData(data);
+        LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+    }
 }
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallActivity.java b/testapps/src/com/android/server/telecom/testapps/TestCallActivity.java
index 38d2565..4ac151f 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallActivity.java
@@ -48,6 +48,9 @@
     public static final String ACTION_HANGUP_CALLS =
             "android.telecom.testapps.ACTION_HANGUP_CALLS";
 
+    public static final String ACTION_SEND_UPGRADE_REQUEST =
+            "android.telecom.testapps.ACTION_SEND_UPGRADE_REQUEST";
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -60,6 +63,8 @@
             CallNotificationReceiver.addNewUnknownCall(this, data, intent.getExtras());
         } else if (ACTION_HANGUP_CALLS.equals(action)) {
             CallNotificationReceiver.hangupCalls(this);
+        } else if (ACTION_SEND_UPGRADE_REQUEST.equals(action)) {
+            CallNotificationReceiver.sendUpgradeRequest(this, data);
         } else {
             CallServiceNotifier.getInstance().updateNotification(this);
         }
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
index 9f5d579..a5d833a 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -129,7 +129,7 @@
         }
     }
 
-    private final class TestConnection extends Connection {
+    final class TestConnection extends Connection {
         private final boolean mIsIncoming;
 
         /** Used to cleanup camera and media when done with connection. */
@@ -144,6 +144,15 @@
             }
         };
 
+        private BroadcastReceiver mUpgradeRequestReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int request = Integer.parseInt(intent.getData().getSchemeSpecificPart());
+                final VideoProfile videoProfile = new VideoProfile(request);
+                mTestVideoCallProvider.receiveSessionModifyRequest(videoProfile);
+            }
+        };
+
         TestConnection(boolean isIncoming) {
             mIsIncoming = isIncoming;
             // Assume all calls are video capable.
@@ -156,6 +165,11 @@
 
             LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(
                     mHangupReceiver, new IntentFilter(TestCallActivity.ACTION_HANGUP_CALLS));
+            final IntentFilter filter =
+                    new IntentFilter(TestCallActivity.ACTION_SEND_UPGRADE_REQUEST);
+            filter.addDataScheme("int");
+            LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(
+                    mUpgradeRequestReceiver, filter);
         }
 
         void startOutgoing() {
@@ -235,6 +249,8 @@
         public void cleanup() {
             LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(
                     mHangupReceiver);
+            LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(
+                    mUpgradeRequestReceiver);
         }
 
         /**
@@ -303,7 +319,7 @@
                     originalRequest.getExtras(),
                     originalRequest.getVideoState());
             connection.setVideoState(originalRequest.getVideoState());
-            maybeAddVideoProvider(connection);
+            addVideoProvider(connection);
             addCall(connection);
             connection.startOutgoing();
 
@@ -341,7 +357,7 @@
             connection.setVideoState(videoState);
             connection.setAddress(address, TelecomManager.PRESENTATION_ALLOWED);
 
-            maybeAddVideoProvider(connection);
+            addVideoProvider(connection);
 
             addCall(connection);
 
@@ -383,15 +399,13 @@
         }
     }
 
-    private void maybeAddVideoProvider(TestConnection connection) {
-        if (connection.getVideoState() == VideoProfile.VideoState.BIDIRECTIONAL) {
-            TestVideoProvider testVideoCallProvider =
-                    new TestVideoProvider(getApplicationContext());
-            connection.setVideoProvider(testVideoCallProvider);
+    private void addVideoProvider(TestConnection connection) {
+        TestVideoProvider testVideoCallProvider =
+                new TestVideoProvider(getApplicationContext(), connection);
+        connection.setVideoProvider(testVideoCallProvider);
 
-            // Keep reference to original so we can clean up the media players later.
-            connection.setTestVideoCallProvider(testVideoCallProvider);
-        }
+        // Keep reference to original so we can clean up the media players later.
+        connection.setTestVideoCallProvider(testVideoCallProvider);
     }
 
     private void activateCall(TestConnection connection) {
@@ -414,7 +428,7 @@
         if (mCalls.isEmpty() && mMediaPlayer != null && mMediaPlayer.isPlaying()) {
             mMediaPlayer.stop();
             mMediaPlayer.release();
-            mMediaPlayer = null;
+            mMediaPlayer = createMediaPlayer();
         }
 
         updateConferenceable();
diff --git a/testapps/src/com/android/server/telecom/testapps/TestVideoProvider.java b/testapps/src/com/android/server/telecom/testapps/TestVideoProvider.java
index 2dad471..f465243 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestVideoProvider.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestVideoProvider.java
@@ -20,6 +20,7 @@
 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
 import com.android.server.telecom.testapps.R;
+import com.android.server.telecom.testapps.TestConnectionService.TestConnection;
 
 import android.content.Context;
 import android.graphics.SurfaceTexture;
@@ -52,6 +53,7 @@
  * Implements the VideoCallProvider.
  */
 public class TestVideoProvider extends Connection.VideoProvider {
+    private TestConnection mConnection;
     private CameraCapabilities mCameraCapabilities;
     private Random random;
     private Surface mDisplaySurface;
@@ -65,11 +67,14 @@
     private CameraCaptureSession mCameraSession;
     private CameraThread mLooperThread;
 
+    private final Handler mHandler = new Handler();
+
     private String mCameraId;
 
     private static final long SESSION_TIMEOUT_MS = 2000;
 
-    public TestVideoProvider(Context context) {
+    public TestVideoProvider(Context context, TestConnection connection) {
+        mConnection = connection;
         mContext = context;
         random = new Random();
         mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
@@ -140,15 +145,22 @@
      * the response back via the CallVideoClient.
      */
     @Override
-    public void onSendSessionModifyRequest(VideoProfile requestProfile) {
+    public void onSendSessionModifyRequest(final VideoProfile requestProfile) {
         log("Sent session modify request");
 
-        VideoProfile responseProfile = new VideoProfile(
-                requestProfile.getVideoState(), requestProfile.getQuality());
-        receiveSessionModifyResponse(
-                SESSION_MODIFY_REQUEST_SUCCESS,
-                requestProfile,
-                responseProfile);
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                final VideoProfile responseProfile = new VideoProfile(
+                        requestProfile.getVideoState(), requestProfile.getQuality());
+                mConnection.setVideoState(requestProfile.getVideoState());
+
+                receiveSessionModifyResponse(
+                        SESSION_MODIFY_REQUEST_SUCCESS,
+                        requestProfile,
+                        responseProfile);
+            }
+        }, 2000);
     }
 
     @Override
diff --git a/tests/src/com/android/server/telecom/tests/CallerInfoAsyncQueryFactoryFixture.java b/tests/src/com/android/server/telecom/tests/CallerInfoAsyncQueryFactoryFixture.java
new file mode 100644
index 0000000..1ebf170
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallerInfoAsyncQueryFactoryFixture.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 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 com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.CallerInfoAsyncQuery;
+import com.android.server.telecom.CallerInfoAsyncQueryFactory;
+import com.android.server.telecom.Log;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controls a test {@link CallerInfoAsyncQueryFactory} to abstract away the asynchronous retrieval
+ * of caller information from the Android contacts database.
+ */
+public class CallerInfoAsyncQueryFactoryFixture implements
+        TestFixture<CallerInfoAsyncQueryFactory> {
+
+    static class Request {
+        int mToken;
+        Object mCookie;
+        CallerInfoAsyncQuery.OnQueryCompleteListener mListener;
+        void reply() {
+            mListener.onQueryComplete(mToken, mCookie, new CallerInfo());
+        }
+    }
+
+    CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory = new CallerInfoAsyncQueryFactory() {
+        @Override
+        public CallerInfoAsyncQuery startQuery(int token, Context context, String number,
+                CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
+            Request r = new Request();
+            r.mToken = token;
+            r.mCookie = cookie;
+            r.mListener = listener;
+            mRequests.add(r);
+            return null;
+        }
+    };
+
+    final List<Request> mRequests = new ArrayList<>();
+
+    public CallerInfoAsyncQueryFactoryFixture() throws Exception {
+        Log.i(this, "Creating ...");
+    }
+
+    @Override
+    public CallerInfoAsyncQueryFactory getTestDouble() {
+        return mCallerInfoAsyncQueryFactory;
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index bf28da7..c83c7d4 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -17,11 +17,15 @@
 package com.android.server.telecom.tests;
 
 import com.android.internal.telecom.IInCallAdapter;
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.CallerInfoAsyncQuery;
+import com.android.server.telecom.CallerInfoAsyncQueryFactory;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.HeadsetMediaButton;
 import com.android.server.telecom.HeadsetMediaButtonFactory;
 import com.android.server.telecom.InCallWakeLockController;
 import com.android.server.telecom.InCallWakeLockControllerFactory;
+import com.android.server.telecom.Log;
 import com.android.server.telecom.MissedCallNotifier;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.ProximitySensorManagerFactory;
@@ -30,7 +34,6 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
-import android.annotation.TargetApi;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -48,6 +51,10 @@
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
 
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.any;
@@ -125,6 +132,8 @@
     ConnectionServiceFixture mConnectionServiceFixtureA;
     ConnectionServiceFixture mConnectionServiceFixtureB;
 
+    CallerInfoAsyncQueryFactoryFixture mCallerInfoAsyncQueryFactoryFixture;
+
     TelecomSystem mTelecomSystem;
 
     @Override
@@ -157,6 +166,8 @@
         InCallWakeLockControllerFactory inCallWakeLockControllerFactory =
                 mock(InCallWakeLockControllerFactory.class);
 
+        mCallerInfoAsyncQueryFactoryFixture = new CallerInfoAsyncQueryFactoryFixture();
+
         when(headsetMediaButtonFactory.create(
                 any(Context.class),
                 any(CallsManager.class)))
@@ -173,6 +184,7 @@
         mTelecomSystem = new TelecomSystem(
                 mComponentContextFixture.getTestDouble(),
                 mMissedCallNotifier,
+                mCallerInfoAsyncQueryFactoryFixture.getTestDouble(),
                 headsetMediaButtonFactory,
                 proximitySensorManagerFactory,
                 inCallWakeLockControllerFactory);
@@ -338,6 +350,32 @@
         return id;
     }
 
+    private void rapidFire(Runnable... tasks) {
+        final CyclicBarrier barrier = new CyclicBarrier(tasks.length);
+        final CountDownLatch latch = new CountDownLatch(tasks.length);
+        for (int i = 0; i < tasks.length; i++) {
+            final Runnable task = tasks[i];
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        barrier.await();
+                        task.run();
+                    } catch (InterruptedException | BrokenBarrierException e){
+                        Log.e(TelecomSystemTest.this, e, "Unexpectedly interrupted");
+                    } finally {
+                        latch.countDown();
+                    }
+                }
+            }).start();
+        }
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            Log.e(TelecomSystemTest.this, e, "Unexpectedly interrupted");
+        }
+    }
+
     // A simple outgoing call, verifying that the appropriate connection service is contacted,
     // the proper lifecycle is followed, and both In-Call Services are updated correctly.
     public void testSingleOutgoingCall() throws Exception {
@@ -398,4 +436,42 @@
         assertEquals(CallState.DISCONNECTED, mInCallServiceFixtureX.getCall(callId).getState());
         assertEquals(CallState.DISCONNECTED, mInCallServiceFixtureY.getCall(callId).getState());
     }
+
+    public void testDeadlockOnOutgoingCall() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            TelecomSystemTest test = new TelecomSystemTest();
+            test.setContext(getContext());
+            test.setTestContext(getTestContext());
+            test.setName(getName());
+            test.setUp();
+            test.do_testDeadlockOnOutgoingCall();
+            test.tearDown();
+        }
+    }
+
+    public void do_testDeadlockOnOutgoingCall() throws Exception {
+        final String connectionId = startOutgoingPhoneCall(
+                "650-555-1212",
+                mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+        rapidFire(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        while (mCallerInfoAsyncQueryFactoryFixture.mRequests.size() > 0) {
+                            mCallerInfoAsyncQueryFactoryFixture.mRequests.remove(0).reply();
+                        }
+                    }
+                },
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            mConnectionServiceFixtureA.sendSetActive(connectionId);
+                        } catch (Exception e) {
+                            Log.e(this, e, "");
+                        }
+                    }
+                });
+    }
 }