Snap for 4683893 from 1f3efbd7efd8d72bafdf5f64bcea2358bbbebdce to pi-release

Change-Id: I1d2676186c9e462db2199e102e223040ab4bedc9
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index f3adb3f..f7f3c79 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -1734,20 +1734,28 @@
         disconnect(0);
     }
 
-    /**
-     * Attempts to disconnect the call through the connection service.
-     */
     @VisibleForTesting
-    public void disconnect(long disconnectionTimeout) {
-        disconnect(disconnectionTimeout, "internal" /** callingPackage */);
+    public void disconnect(String reason) {
+        disconnect(0, reason);
     }
 
     /**
      * Attempts to disconnect the call through the connection service.
      */
     @VisibleForTesting
-    public void disconnect(long disconnectionTimeout, String callingPackage) {
-        Log.addEvent(this, LogUtils.Events.REQUEST_DISCONNECT, callingPackage);
+    public void disconnect(long disconnectionTimeout) {
+        disconnect(disconnectionTimeout, "internal" /** reason */);
+    }
+
+    /**
+     * Attempts to disconnect the call through the connection service.
+     * @param reason the reason for the disconnect; used for logging purposes only.  In some cases
+     *               this can be a package name if the disconnect was initiated through an API such
+     *               as TelecomManager.
+     */
+    @VisibleForTesting
+    public void disconnect(long disconnectionTimeout, String reason) {
+        Log.addEvent(this, LogUtils.Events.REQUEST_DISCONNECT, reason);
 
         // Track that the call is now locally disconnecting.
         setLocallyDisconnecting(true);
@@ -1868,7 +1876,7 @@
      */
     @VisibleForTesting
     public void reject(boolean rejectWithMessage, String textMessage) {
-        reject(rejectWithMessage, textMessage, "internal" /** callingPackage */);
+        reject(rejectWithMessage, textMessage, "internal" /** reason */);
     }
 
     /**
@@ -1876,9 +1884,11 @@
      *
      * @param rejectWithMessage Whether to send a text message as part of the call rejection.
      * @param textMessage An optional text message to send as part of the rejection.
+     * @param reason The reason for the reject; used for logging purposes.  May be a package name
+     *               if the reject is initiated from an API such as TelecomManager.
      */
     @VisibleForTesting
-    public void reject(boolean rejectWithMessage, String textMessage, String callingPackage) {
+    public void reject(boolean rejectWithMessage, String textMessage, String reason) {
         // Check to verify that the call is still in the ringing state. A call can change states
         // between the time the user hits 'reject' and Telecomm receives the command.
         if (isRinging("reject")) {
@@ -1891,7 +1901,7 @@
                 Log.e(this, new NullPointerException(),
                         "reject call failed due to null CS callId=%s", getId());
             }
-            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, callingPackage);
+            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
         }
     }
 
@@ -1900,6 +1910,10 @@
      */
     @VisibleForTesting
     public void hold() {
+        hold(null /* reason */);
+    }
+
+    public void hold(String reason) {
         if (mState == CallState.ACTIVE) {
             if (mConnectionService != null) {
                 mConnectionService.hold(this);
@@ -1907,7 +1921,7 @@
                 Log.e(this, new NullPointerException(),
                         "hold call failed due to null CS callId=%s", getId());
             }
-            Log.addEvent(this, LogUtils.Events.REQUEST_HOLD);
+            Log.addEvent(this, LogUtils.Events.REQUEST_HOLD, reason);
         }
     }
 
@@ -1916,6 +1930,10 @@
      */
     @VisibleForTesting
     public void unhold() {
+        unhold(null /* reason */);
+    }
+
+    public void unhold(String reason) {
         if (mState == CallState.ON_HOLD) {
             if (mConnectionService != null) {
                 mConnectionService.unhold(this);
@@ -1923,7 +1941,7 @@
                 Log.e(this, new NullPointerException(),
                         "unhold call failed due to null CS callId=%s", getId());
             }
-            Log.addEvent(this, LogUtils.Events.REQUEST_UNHOLD);
+            Log.addEvent(this, LogUtils.Events.REQUEST_UNHOLD, reason);
         }
     }
 
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 7bc2519..56f8db9 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -388,7 +388,8 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void onRingerModeChange() {
-        mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.RINGER_MODE_CHANGE);
+        mCallAudioModeStateMachine.sendMessageWithArgs(
+                CallAudioModeStateMachine.RINGER_MODE_CHANGE, makeArgsForModeStateMachine());
     }
 
     @VisibleForTesting
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index ac3500e..334667c 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -286,7 +286,8 @@
                         ConnectionServiceFocusManager.ConnectionServiceFocus connectionService) {
                     mCalls.stream()
                             .filter(c -> c.getConnectionServiceWrapper().equals(connectionService))
-                            .forEach(c -> c.disconnect());
+                            .forEach(c -> c.disconnect("release " +
+                                    connectionService.getComponentName().getPackageName()));
                 }
 
                 @Override
@@ -1491,7 +1492,7 @@
                     // service, then disconnect it, otherwise allow the connection service to
                     // figure out the right states.
                     if (activeCall.getConnectionService() != call.getConnectionService()) {
-                        activeCall.disconnect();
+                        activeCall.disconnect("Can't hold when answering " + call.getId());
                     }
                 }
             }
@@ -1683,25 +1684,27 @@
             Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
         } else {
             Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+            String activeCallId = null;
             if (activeCall != null) {
+                activeCallId = activeCall.getId();
                 if (canHold(activeCall)) {
-                    activeCall.hold();
-                    Log.addEvent(activeCall, LogUtils.Events.SWAP);
-                    Log.addEvent(call, LogUtils.Events.SWAP);
+                    activeCall.hold("Swap to " + call.getId());
+                    Log.addEvent(activeCall, LogUtils.Events.SWAP, "To " + call.getId());
+                    Log.addEvent(call, LogUtils.Events.SWAP, "From " + activeCall.getId());
                 } else {
                     // This call does not support hold. If it is from a different connection
                     // service, then disconnect it, otherwise invoke call.hold() and allow the
                     // connection service to handle the situation.
                     if (activeCall.getConnectionService() != call.getConnectionService()) {
-                        activeCall.disconnect();
+                        activeCall.disconnect("Swap to " + call.getId());
                     } else {
-                        activeCall.hold();
+                        activeCall.hold("Swap to " + call.getId());
                     }
                 }
             }
             mConnectionSvrFocusMgr.requestFocus(
                     call,
-                    new RequestCallback(new ActionUnHoldCall(call)));
+                    new RequestCallback(new ActionUnHoldCall(call, activeCallId)));
         }
     }
 
@@ -1879,7 +1882,7 @@
             if (makeRoomForOutgoingCall(call, false /* isEmergencyCall */)) {
                 call.startCreateConnection(mPhoneAccountRegistrar);
             } else {
-                call.disconnect();
+                call.disconnect("no room");
             }
 
             if (setDefault) {
@@ -2838,7 +2841,7 @@
             if (isEmergency && !canHold(liveCall)) {
                 call.getAnalytics().setCallIsAdditional(true);
                 liveCall.getAnalytics().setCallIsInterrupted(true);
-                liveCall.disconnect();
+                liveCall.disconnect("emergency, can't hold");
                 return true;
             }
 
@@ -2881,7 +2884,7 @@
                 Log.i(this, "makeRoomForOutgoingCall: holding live call.");
                 call.getAnalytics().setCallIsAdditional(true);
                 liveCall.getAnalytics().setCallIsInterrupted(true);
-                liveCall.hold();
+                liveCall.hold("calling " + call.getId());
                 return true;
             }
 
@@ -3180,7 +3183,7 @@
 
             // We are going to place the new outgoing call, so disconnect any ongoing self-managed
             // calls which are ongoing at this time.
-            disconnectSelfManagedCalls();
+            disconnectSelfManagedCalls("outgoing call " + callId);
 
             // Kick of the new outgoing call intent from where it left off prior to confirming the
             // call.
@@ -3242,14 +3245,14 @@
     /**
      * Disconnects all self-managed calls.
      */
-    private void disconnectSelfManagedCalls() {
+    private void disconnectSelfManagedCalls(String reason) {
         // Disconnect all self-managed calls to make priority for emergency call.
         // Use Call.disconnect() to command the ConnectionService to disconnect the calls.
         // CallsManager.markCallAsDisconnected doesn't actually tell the ConnectionService to
         // disconnect.
         mCalls.stream()
                 .filter(c -> c.isSelfManaged())
-                .forEach(c -> c.disconnect());
+                .forEach(c -> c.disconnect(reason));
 
         // When disconnecting all self-managed calls, switch audio routing back to the baseline
         // route.  This ensures if, for example, the self-managed ConnectionService was routed to
@@ -3259,11 +3262,13 @@
     }
 
     private void disconnectCallsHaveDifferentConnectionService(Call exceptCall) {
+        String csPackage = exceptCall.getConnectionService() != null ?
+                exceptCall.getConnectionService().getComponentName().toShortString() : "null";
         mCalls.stream().filter(c ->
                 c.getConnectionService() != exceptCall.getConnectionService()
                         && c.getConnectionManagerPhoneAccount()
                         != exceptCall.getConnectionManagerPhoneAccount())
-                .forEach(c -> c.disconnect());
+                .forEach(c -> c.disconnect("CS not " + csPackage));
     }
 
     /**
@@ -3392,7 +3397,7 @@
         ConnectionServiceWrapper service = call.getConnectionService();
         service.handoverFailed(call, reason);
         call.setDisconnectCause(new DisconnectCause(DisconnectCause.CANCELED));
-        call.disconnect();
+        call.disconnect("handover failed");
     }
 
     /**
@@ -3572,7 +3577,7 @@
         } else {
             if (call.isEmergencyCall()) {
                 // Disconnect all self-managed calls to make priority for emergency call.
-                disconnectSelfManagedCalls();
+                disconnectSelfManagedCalls("emergency call");
             }
 
             call.startCreateConnection(mPhoneAccountRegistrar);
@@ -3789,15 +3794,17 @@
 
     private final class ActionUnHoldCall implements PendingAction {
         private final Call mCall;
+        private final String mPreviouslyHeldCallId;
 
-        ActionUnHoldCall(Call call) {
+        ActionUnHoldCall(Call call, String previouslyHeldCallId) {
             mCall = call;
+            mPreviouslyHeldCallId = previouslyHeldCallId;
         }
 
         @Override
         public void performAction() {
             Log.d(this, "perform unhold call for %s", mCall);
-            mCall.unhold();
+            mCall.unhold("held " + mPreviouslyHeldCallId);
         }
     }
 
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index f296502..649a9d1 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom;
 
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -64,6 +65,12 @@
          * @see {@link ConnectionServiceFocusListener}.
          */
         void setConnectionServiceFocusListener(ConnectionServiceFocusListener listener);
+
+        /**
+         * Get the {@link ComponentName} of the ConnectionService for logging purposes.
+         * @return the {@link ComponentName}.
+         */
+        ComponentName getComponentName();
     }
 
     /**
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index 44ddc24..f7844b4 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -309,7 +309,7 @@
         }
     }
 
-    final ComponentName getComponentName() {
+    public final ComponentName getComponentName() {
         return mComponentName;
     }
 
diff --git a/testapps/res/layout/self_managed_call_list_item.xml b/testapps/res/layout/self_managed_call_list_item.xml
index 7e149a8..66b5b21 100644
--- a/testapps/res/layout/self_managed_call_list_item.xml
+++ b/testapps/res/layout/self_managed_call_list_item.xml
@@ -72,5 +72,10 @@
             android:layout_height="wrap_content"
             android:text="Earpiece"
             android:id="@+id/earpieceButton" />
+        <CheckBox
+            android:id="@+id/holdable"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" android:layout_weight="1"
+            android:text="Holdable"/>
     </LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/testapps/res/layout/self_managed_sample_main.xml b/testapps/res/layout/self_managed_sample_main.xml
index e55de33..68ae65c 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -30,43 +30,42 @@
         android:layout_height="wrap_content"
         android:text="This app provides two sample implementations of the self-managed ConnectionService API.  Use this UI to add simulated self-managed calls:" />
 
-    <RadioGroup
+    <LinearLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="horizontal">
-        <RadioButton
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Sample Source 1"
-            android:id="@+id/useAcct1Button"
-            android:background="@color/test_call_a_color"/>
-        <RadioButton
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Sample Source 2"
-            android:id="@+id/useAcct2Button"
-            android:background="@color/test_call_b_color"/>
-    </RadioGroup>
-
-    <RadioGroup
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-        <RadioButton
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Video Call"
-            android:id="@+id/videoCallButton"/>
-        <RadioButton
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Audio Call"
-            android:id="@+id/audioCallButton"/>
-    </RadioGroup>
+        <TextView
+            android:id="@+id/textView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" android:layout_weight="1"
+            android:text="Acct:"/>
+        <RadioGroup
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1" android:orientation="horizontal">
+            <RadioButton
+                android:id="@+id/useAcct1Button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@color/test_call_a_color"
+                android:checked="true" android:text="1"/>
+            <RadioButton
+                android:id="@+id/useAcct2Button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@color/test_call_b_color"
+                android:text="2"/>
+        </RadioGroup>
+        <TextView
+            android:id="@+id/hasFocus"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" android:layout_weight="1"
+            android:text="👎 No Focus 👎"/>
+    </LinearLayout>
 
     <LinearLayout android:orientation="horizontal"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content">
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
         <TextView
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
@@ -76,6 +75,16 @@
             android:layout_height="wrap_content"
             android:id="@+id/phoneNumber"
             android:text="tel:555-1212"/>
+        <CheckBox
+            android:id="@+id/holdable"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" android:layout_weight="1"
+            android:checked="true" android:text="Holdable"/>
+        <CheckBox
+            android:id="@+id/videoCall"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" android:layout_weight="1"
+            android:text="Video"/>
     </LinearLayout>
 
     <LinearLayout android:orientation="horizontal"
@@ -83,20 +92,20 @@
         android:layout_height="wrap_content">
 
         <Button
+            android:id="@+id/placeOutgoingCallButton"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="Outgoing Call"
-            android:id="@+id/placeOutgoingCallButton" />
+            android:text="Outgoing"/>
         <Button
+            android:id="@+id/placeIncomingCallButton"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="Incoming Call"
-            android:id="@+id/placeIncomingCallButton" />
+            android:text="Incoming"/>
         <Button
+            android:id="@+id/handoverFrom"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="Handover From"
-            android:id="@+id/handoverFrom" />
+            android:text="Accept Handover"/>
     </LinearLayout>
 
     <ListView
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index f9bce35..8518adc 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -40,6 +40,8 @@
         public void onCreateIncomingConnectionFailed(ConnectionRequest request) {};
         public void onCreateOutgoingConnectionFailed(ConnectionRequest request) {};
         public void onConnectionListChanged() {};
+        public void onConnectionServiceFocusLost() {};
+        public void onConnectionServiceFocusGained() {};
     }
 
     public static String SELF_MANAGED_ACCOUNT_1 = "1";
@@ -136,6 +138,18 @@
         }
     }
 
+    public void notifyConnectionServiceFocusGained() {
+        if (mListener != null) {
+            mListener.onConnectionServiceFocusGained();
+        }
+    }
+
+    public void notifyConnectionServiceFocusLost() {
+        if (mListener != null) {
+            mListener.onConnectionServiceFocusLost();
+        }
+    }
+
     public void addConnection(SelfManagedConnection connection) {
         Log.i(this, "addConnection %s", connection);
         mConnections.add(connection);
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
index 71e8922..8eaa282 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom.testapps;
 
 import android.telecom.CallAudioState;
+import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccountHandle;
 import android.util.Log;
@@ -24,6 +25,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
+import android.widget.CheckBox;
 import android.widget.TextView;
 
 import com.android.server.telecom.testapps.R;
@@ -102,6 +104,22 @@
         }
     };
 
+    private View.OnClickListener mHoldableListener = new View.OnClickListener() {
+        @Override
+        public void onClick (View v) {
+            View parent = (View) v.getParent().getParent();
+            SelfManagedConnection connection = (SelfManagedConnection) parent.getTag();
+            int capabilities = connection.getConnectionCapabilities();
+            if ((capabilities & Connection.CAPABILITY_HOLD) == Connection.CAPABILITY_HOLD) {
+                capabilities &= ~(Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD);
+            } else {
+                capabilities |= (Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD);
+            }
+            connection.setConnectionCapabilities(capabilities);
+            notifyDataSetChanged();
+        }
+    };
+
     private final LayoutInflater mLayoutInflater;
 
     private List<SelfManagedConnection> mConnections;
@@ -175,7 +193,8 @@
         }
         setInfoForRow(result, phoneAccountHandle.getId(), connection.getAddress().toString(),
                 android.telecom.Connection.stateToString(connection.getState()), audioRoute,
-                callType, connection.getState() == android.telecom.Connection.STATE_RINGING);
+                callType, connection.getState() == android.telecom.Connection.STATE_RINGING, 
+                connection.isHoldable());
         result.setTag(connection);
         return result;
     }
@@ -188,7 +207,7 @@
 
     private void setInfoForRow(View view, String accountName, String number,
                                String status, String audioRoute, String callType,
-            boolean isRinging) {
+            boolean isRinging, boolean isHoldable) {
 
         TextView numberTextView = (TextView) view.findViewById(R.id.phoneNumber);
         TextView statusTextView = (TextView) view.findViewById(R.id.callState);
@@ -207,6 +226,9 @@
         missedButton.setVisibility(isRinging ? View.VISIBLE : View.GONE);
         setHeldButton.setVisibility(!isRinging ? View.VISIBLE : View.GONE);
         disconnectButton.setVisibility(!isRinging ? View.VISIBLE : View.GONE);
+        CheckBox holdableCheckbox = view.findViewById(R.id.holdable);
+        holdableCheckbox.setOnClickListener(mHoldableListener);
+        holdableCheckbox.setChecked(isHoldable);
         numberTextView.setText(accountName + " - " + number + " (" + audioRoute + ")");
         statusTextView.setText(callType + " - Status: " + status);
     }
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index 6139e33..a7b1350 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -31,6 +31,7 @@
 import android.widget.EditText;
 import android.widget.ListView;
 import android.widget.RadioButton;
+import android.widget.TextView;
 import android.widget.Toast;
 
 import com.android.server.telecom.testapps.R;
@@ -50,10 +51,12 @@
     private Button mHandoverFrom;
     private RadioButton mUseAcct1Button;
     private RadioButton mUseAcct2Button;
-    private RadioButton mVideoCallButton;
-    private RadioButton mAudioCallButton;
+    private CheckBox mHoldableCheckbox;
+    private CheckBox mVideoCallCheckbox;
     private EditText mNumber;
     private ListView mListView;
+    private TextView mHasFocus;
+
     private SelfManagedCallListAdapter mListAdapter;
 
     private SelfManagedCallList.Listener mCallListListener = new SelfManagedCallList.Listener() {
@@ -76,6 +79,16 @@
             Log.i(TAG, "onConnectionListChanged");
             mListAdapter.updateConnections();
         };
+
+        @Override
+        public void onConnectionServiceFocusLost() {
+            mHasFocus.setText("\uD83D\uDC4E No Focus \uD83D\uDC4E");
+        };
+
+        @Override
+        public void onConnectionServiceFocusGained() {
+            mHasFocus.setText("\uD83D\uDC4D Has Focus \uD83D\uDC4D");
+        };
     };
 
     @Override
@@ -109,10 +122,11 @@
             placeIncomingCall(true /* isHandoverFrom */);
         }));
 
-        mUseAcct1Button = (RadioButton) findViewById(R.id.useAcct1Button);
-        mUseAcct2Button = (RadioButton) findViewById(R.id.useAcct2Button);
-        mVideoCallButton = (RadioButton) findViewById(R.id.videoCallButton);
-        mAudioCallButton = (RadioButton) findViewById(R.id.audioCallButton);
+        mUseAcct1Button = findViewById(R.id.useAcct1Button);
+        mUseAcct2Button = findViewById(R.id.useAcct2Button);
+        mHasFocus = findViewById(R.id.hasFocus);
+        mVideoCallCheckbox = findViewById(R.id.videoCall);
+        mHoldableCheckbox = findViewById(R.id.holdable);
         mNumber = (EditText) findViewById(R.id.phoneNumber);
         mListView = (ListView) findViewById(R.id.callList);
         mCallList.setListener(mCallListListener);
@@ -146,10 +160,14 @@
         Bundle extras = new Bundle();
         extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
                 getSelectedPhoneAccountHandle());
-        if (mVideoCallButton.isChecked()) {
+        if (mVideoCallCheckbox.isChecked()) {
             extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                     VideoProfile.STATE_BIDIRECTIONAL);
         }
+        Bundle clientExtras = new Bundle();
+        clientExtras.putBoolean(SelfManagedConnectionService.EXTRA_HOLDABLE,
+                mHoldableCheckbox.isChecked());
+        extras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, clientExtras);
         tm.placeCall(Uri.parse(mNumber.getText().toString()), extras);
     }
 
@@ -167,7 +185,9 @@
         Bundle extras = new Bundle();
         extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
                 Uri.parse(mNumber.getText().toString()));
-        if (mVideoCallButton.isChecked()) {
+        extras.putBoolean(SelfManagedConnectionService.EXTRA_HOLDABLE,
+                mHoldableCheckbox.isChecked());
+        if (mVideoCallCheckbox.isChecked()) {
             extras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE,
                     VideoProfile.STATE_BIDIRECTIONAL);
         }
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
index a84dd90..8d0af04 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
@@ -138,11 +138,17 @@
 
     @Override
     public void onHold() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.pause();
+        }
         setOnHold();
     }
 
     @Override
     public void onUnhold() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.start();
+        }
         setActive();
     }
 
@@ -218,6 +224,10 @@
         return mIsHandover;
     }
 
+    public boolean isHoldable() {
+        return (getConnectionCapabilities() & Connection.CAPABILITY_HOLD) != 0;
+    }
+
     private MediaPlayer createMediaPlayer(Context context) {
         int audioToPlay = (Math.random() > 0.5f) ? R.raw.sample_audio : R.raw.sample_audio2;
         MediaPlayer mediaPlayer = MediaPlayer.create(context, audioToPlay);
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index 12d1552..d5d79af 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -35,6 +35,7 @@
  * See {@link android.telecom} for more information on self-managed {@link ConnectionService}s.
  */
 public class SelfManagedConnectionService extends ConnectionService {
+    public static final String EXTRA_HOLDABLE = "com.android.server.telecom.testapps.HOLDABLE";
     private static final String[] TEST_NAMES = {"Tom Smith", "Jane Appleseed", "Joseph Engleton",
             "Claudia McPherson", "Chris P. Bacon", "Seymour Butz", "Hugh Mungus", "Anita Bath"};
     private final SelfManagedCallList mCallList = SelfManagedCallList.getInstance();
@@ -65,6 +66,17 @@
         mCallList.notifyCreateOutgoingConnectionFailed(request);
     }
 
+    @Override
+    public void onConnectionServiceFocusLost() {
+        mCallList.notifyConnectionServiceFocusLost();
+        connectionServiceFocusReleased();
+    }
+
+    @Override
+    public void onConnectionServiceFocusGained() {
+        mCallList.notifyConnectionServiceFocusGained();
+    }
+
     private Connection createSelfManagedConnection(ConnectionRequest request, boolean isIncoming) {
         SelfManagedConnection connection = new SelfManagedConnection(mCallList,
                 getApplicationContext(), isIncoming);
@@ -83,11 +95,17 @@
         }
         Bundle requestExtras = request.getExtras();
         if (requestExtras != null) {
-            Log.i(this, "createConnection: isHandover=%b, handoverFrom=%s",
+            boolean isHoldable = requestExtras.getBoolean(EXTRA_HOLDABLE, false);
+            Log.i(this, "createConnection: isHandover=%b, handoverFrom=%s, holdable=%b",
                     requestExtras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER),
-                    requestExtras.getString(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT));
+                    requestExtras.getString(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT),
+                    isHoldable);
             connection.setIsHandover(requestExtras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER,
                     false));
+            if (isHoldable) {
+                connection.setConnectionCapabilities(connection.getConnectionCapabilities() |
+                        Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD);
+            }
             if (!isIncoming && connection.isHandover()) {
                 Intent intent = new Intent(Intent.ACTION_MAIN, null);
                 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);