Add Self-Managed CTS Verifier Test.

Adding CTS to ensure incoming call ui for self-managed calls is shown.

Test: THIS is a test.
Bug: 36098357
Change-Id: Icc6be41a19035444121d98927059e84f2e9d7f95
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index fea3b2b..bf4e30b 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -69,6 +69,9 @@
     <!-- Needed by the Audio Quality Verifier to store the sound samples that will be mailed. -->
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
+    <!-- Needed for Telecom self-managed ConnectionService tests. -->
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
+
     <application android:label="@string/app_name"
             android:icon="@drawable/icon"
             android:backupAgent="VerifierBackupAgent"
@@ -3011,6 +3014,22 @@
             <meta-data
                 android:name="test_required_features"
                 android:value="android.hardware.telephony"/>
+            </activity>
+
+        <activity
+            android:name=".telecom.SelfManagedIncomingCallTestActivity"
+            android:label="@string/telecom_incoming_self_mgd_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.cts.intent.category.MANUAL_TEST"/>
+            </intent-filter>
+
+            <meta-data
+                android:name="test_category"
+                android:value="@string/test_category_telecom"/>
+            <meta-data
+                android:name="test_required_features"
+                android:value="android.hardware.telephony"/>
         </activity>
 
         <activity
diff --git a/apps/CtsVerifier/res/layout/telecom_self_managed_answer.xml b/apps/CtsVerifier/res/layout/telecom_self_managed_answer.xml
new file mode 100644
index 0000000..27aa55a
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/telecom_self_managed_answer.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 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
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/telecom_incoming_self_mgd_info"/>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/js_padding"
+        android:layout_marginBottom="@dimen/js_padding">
+
+        <ImageView
+            android:id="@+id/step_1_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/fs_indeterminate"
+            android:layout_marginRight="@dimen/js_padding"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentTop="true" />
+        <TextView
+            android:id="@+id/step_1_instructions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/telecom_incoming_self_mgd_step_1"
+            android:textSize="16dp"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentTop="true"
+            android:layout_toRightOf="@id/step_1_status" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_below="@id/step_1_instructions"
+            android:layout_marginLeft="20dip"
+            android:layout_marginRight="20dip"
+            android:layout_toRightOf="@id/step_1_status"
+            android:id="@+id/telecom_incoming_self_mgd_register_button"
+            android:text="@string/telecom_incoming_self_mgd_register_button"/>
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/js_padding"
+        android:layout_marginBottom="@dimen/js_padding">
+
+        <ImageView
+            android:id="@+id/step_2_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/fs_indeterminate"
+            android:layout_marginRight="@dimen/js_padding"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentTop="true" />
+        <TextView
+            android:id="@+id/step_2_instructions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/telecom_incoming_self_mgd_step_2"
+            android:textSize="16dp"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentTop="true"
+            android:layout_toRightOf="@id/step_2_status" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_below="@id/step_2_instructions"
+            android:layout_marginLeft="20dip"
+            android:layout_marginRight="20dip"
+            android:layout_toRightOf="@id/step_2_status"
+            android:id="@+id/telecom_incoming_self_mgd_show_ui_button"
+            android:text="@string/telecom_incoming_self_mgd_show_ui_button"/>
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/js_padding"
+        android:layout_marginBottom="@dimen/js_padding">
+
+        <ImageView
+            android:id="@+id/step_3_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/fs_indeterminate"
+            android:layout_marginRight="@dimen/js_padding"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentTop="true" />
+        <TextView
+            android:id="@+id/step_3_instructions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/telecom_incoming_self_mgd_step_3"
+            android:textSize="16dp"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentTop="true"
+            android:layout_toRightOf="@id/step_3_status" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_below="@id/step_3_instructions"
+            android:layout_marginLeft="20dip"
+            android:layout_marginRight="20dip"
+            android:layout_toRightOf="@id/step_3_status"
+            android:id="@+id/telecom_incoming_self_mgd_confirm_answer_button"
+            android:text="@string/telecom_incoming_self_mgd_confirm_answer_button"/>
+    </RelativeLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index f08bc30..f34bf48 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -3831,4 +3831,22 @@
         audio was audible.
     </string>
     <string name="telecom_incoming_call_confirm_button">Confirm</string>
+    <string name="telecom_incoming_self_mgd_test"> Incoming Self-Managed Connection Test</string>
+    <string name="telecom_incoming_self_mgd_info">
+        This test verifies that incoming calls from a Self-Managed Connection Service will trigger
+        a Telecom-managed incoming call UI when there is already an ongoing call on the device.
+    </string>
+    <string name="telecom_incoming_self_mgd_step_1">
+        Click the button below to register a test self-managed ConnectionService.
+    </string>
+    <string name="telecom_incoming_self_mgd_register_button">Register Self-Managed ConnectionService</string>
+    <string name="telecom_incoming_self_mgd_step_2">
+        Click the button below to test that the system incoming call notification shows.  When the
+        notification shows, "answer" the call.
+    </string>
+    <string name="telecom_incoming_self_mgd_show_ui_button">Show System Incoming UI</string>
+    <string name="telecom_incoming_self_mgd_step_3">
+        Click the button below to confirm that the incoming call was answered.
+    </string>
+    <string name="telecom_incoming_self_mgd_confirm_answer_button">Confirm Answer</string>
 </resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnection.java b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnection.java
index c935841..c004b73 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnection.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnection.java
@@ -20,12 +20,16 @@
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.MediaPlayer;
+import android.telecom.CallAudioState;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.VideoProfile;
 
 import com.android.cts.verifier.R;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * An implementation of the {@link android.telecom.Connection} class used by the
  * {@link CtsConnectionService}.
@@ -51,6 +55,7 @@
     private final Listener mListener;
     private final MediaPlayer mMediaPlayer;
     private final Context mContext;
+    private CountDownLatch mWaitForCallAudioStateChanged = new CountDownLatch(1);
 
     public CtsConnection(Context context, boolean isIncomingCall,
             Listener listener, boolean hasAudio) {
@@ -131,6 +136,20 @@
         }
     }
 
+    public void onCallAudioStateChanged(CallAudioState state) {
+        mWaitForCallAudioStateChanged.countDown();
+        mWaitForCallAudioStateChanged = new CountDownLatch(1);
+
+    }
+
+    public void waitForAudioStateChanged() {
+        try {
+            mWaitForCallAudioStateChanged.await(CtsConnectionService.TIMEOUT_MILLIS,
+                    TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+        }
+    }
+
     private void setDisconnectedAndDestroy(DisconnectCause cause) {
         setDisconnected(cause);
         destroy();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnectionService.java b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnectionService.java
index 528c221..7cfcbf8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnectionService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/CtsConnectionService.java
@@ -26,35 +26,74 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * CTS Verifier ConnectionService implementation.
  */
 public class CtsConnectionService extends ConnectionService {
+    static final int TIMEOUT_MILLIS = 10000;
 
     private CtsConnection.Listener mConnectionListener =
             new CtsConnection.Listener() {
                 @Override
                 void onDestroyed(CtsConnection connection) {
-                    mConnections.remove(connection);
+                    synchronized (mConnectionsLock) {
+                        mConnections.remove(connection);
+                    }
                 }
             };
 
     private static CtsConnectionService sConnectionService;
+    private static CountDownLatch sBindingLatch = new CountDownLatch(1);
 
     private List<CtsConnection> mConnections = new ArrayList<>();
+    private Object mConnectionsLock = new Object();
+    private CountDownLatch mConnectionLatch = new CountDownLatch(1);
 
     public static CtsConnectionService getConnectionService() {
         return sConnectionService;
     }
 
+    public static CtsConnectionService waitForAndGetConnectionService() {
+        if (sConnectionService == null) {
+            try {
+                sBindingLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+            }
+        }
+        return sConnectionService;
+    }
+
     public CtsConnectionService() throws Exception {
         super();
         sConnectionService = this;
+        if (sBindingLatch != null) {
+            sBindingLatch.countDown();
+        }
+        sBindingLatch = new CountDownLatch(1);
     }
 
     public List<CtsConnection> getConnections() {
-        return mConnections;
+        synchronized (mConnectionsLock) {
+            return new ArrayList<CtsConnection>(mConnections);
+        }
+    }
+
+    public CtsConnection waitForAndGetConnection() {
+        try {
+            mConnectionLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+        }
+        mConnectionLatch = new CountDownLatch(1);
+        synchronized (mConnectionsLock) {
+            if (mConnections.size() > 0) {
+                return mConnections.get(0);
+            } else {
+                return null;
+            }
+        }
     }
 
     @Override
@@ -109,7 +148,12 @@
         connection.putExtras(moreExtras);
         connection.setVideoState(request.getVideoState());
 
-        mConnections.add(connection);
+        synchronized (mConnectionsLock) {
+            mConnections.add(connection);
+        }
+        if (mConnectionLatch != null) {
+            mConnectionLatch.countDown();
+        }
         return connection;
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/PhoneAccountUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/PhoneAccountUtils.java
index aa8e961..b559373 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/PhoneAccountUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/PhoneAccountUtils.java
@@ -57,6 +57,19 @@
             .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
             .build();
 
+    public static final String TEST_SELF_MAANGED_PHONE_ACCOUNT2_ID = "selfMgdTest2";
+    public static final String TEST_SELF_MANAGED_PHONE_ACCOUNT2_LABEL = "CTSVerifier2";
+
+    public static final PhoneAccountHandle TEST_SELF_MANAGED_PHONE_ACCOUNT_HANDLE_2 =
+            new PhoneAccountHandle(new ComponentName(
+                    PassFailButtons.class.getPackage().getName(),
+                    CtsConnectionService.class.getName()), TEST_SELF_MAANGED_PHONE_ACCOUNT2_ID);
+    public static final PhoneAccount TEST_SELF_MANAGED_PHONE_ACCOUNT_2 = new PhoneAccount.Builder(
+            TEST_SELF_MANAGED_PHONE_ACCOUNT_HANDLE_2, TEST_SELF_MANAGED_PHONE_ACCOUNT2_LABEL)
+            .setAddress(TEST_SELF_MANAGED_PHONE_ACCOUNT_ADDRESS)
+            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+            .build();
+
     /**
      * Registers the test phone account.
      * @param context The context.
@@ -96,6 +109,7 @@
         TelecomManager telecomManager = (TelecomManager) context.getSystemService(
                 Context.TELECOM_SERVICE);
         telecomManager.registerPhoneAccount(TEST_SELF_MANAGED_PHONE_ACCOUNT);
+        telecomManager.registerPhoneAccount(TEST_SELF_MANAGED_PHONE_ACCOUNT_2);
     }
 
     /**
@@ -114,4 +128,15 @@
                 Context.TELECOM_SERVICE);
         return telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
     }
+
+    /**
+     * Retrieves the test phone account, or null if not registered.
+     * @param context The context.
+     * @return The Phone Account.
+     */
+    public static PhoneAccount getSelfManagedPhoneAccount2(Context context) {
+        TelecomManager telecomManager = (TelecomManager) context.getSystemService(
+                Context.TELECOM_SERVICE);
+        return telecomManager.getPhoneAccount(TEST_SELF_MANAGED_PHONE_ACCOUNT_HANDLE_2);
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/SelfManagedIncomingCallTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/SelfManagedIncomingCallTestActivity.java
new file mode 100644
index 0000000..530b246
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/SelfManagedIncomingCallTestActivity.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.telecom;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.telecom.Connection;
+import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.List;
+
+/**
+ * This test verifies functionality associated with the Self-Managed
+ * {@link android.telecom.ConnectionService} APIs.  It ensures that Telecom will show an incoming
+ * call UI when a new incoming self-managed call is added when there is already an ongoing managed
+ * call or when there is an ongoing self-managed call in another app.
+ */
+public class SelfManagedIncomingCallTestActivity extends PassFailButtons.Activity {
+    private Uri TEST_DIAL_NUMBER_1 = Uri.fromParts("tel", "6505551212", null);
+    private Uri TEST_DIAL_NUMBER_2 = Uri.fromParts("tel", "4085551212", null);
+
+    private ImageView mStep1Status;
+    private Button mRegisterPhoneAccount;
+    private ImageView mStep2Status;
+    private Button mShowUi;
+    private ImageView mStep3Status;
+    private Button mConfirm;
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        View view = getLayoutInflater().inflate(R.layout.telecom_self_managed_answer, null);
+        setContentView(view);
+        setInfoResources(R.string.telecom_incoming_self_mgd_test,
+                R.string.telecom_incoming_self_mgd_info, -1);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mStep1Status = view.findViewById(R.id.step_1_status);
+        mRegisterPhoneAccount = view.findViewById(R.id.telecom_incoming_self_mgd_register_button);
+        mRegisterPhoneAccount.setOnClickListener(v -> {
+            PhoneAccountUtils.registerTestSelfManagedPhoneAccount(this);
+            PhoneAccount account = PhoneAccountUtils.getSelfManagedPhoneAccount(this);
+            PhoneAccount account2 = PhoneAccountUtils.getSelfManagedPhoneAccount2(this);
+            if (account != null &&
+                    account.isEnabled() &&
+                    account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED) &&
+                    account2 != null &&
+                    account2.isEnabled() &&
+                    account2.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
+                mRegisterPhoneAccount.setEnabled(false);
+                mShowUi.setEnabled(true);
+                mStep1Status.setImageResource(R.drawable.fs_good);
+            } else {
+                mStep1Status.setImageResource(R.drawable.fs_error);
+            }
+        });
+
+        mStep2Status = view.findViewById(R.id.step_2_status);
+        mShowUi = view.findViewById(R.id.telecom_incoming_self_mgd_show_ui_button);
+        mShowUi.setOnClickListener(v -> {
+            (new AsyncTask<Void, Void, Throwable>() {
+                @Override
+                protected Throwable doInBackground(Void... params) {
+                    try {
+                        Bundle extras = new Bundle();
+                        extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
+                                TEST_DIAL_NUMBER_1);
+                        TelecomManager telecomManager =
+                                (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
+                        if (telecomManager == null) {
+                            mStep2Status.setImageResource(R.drawable.fs_error);
+                            return new Throwable("Could not get telecom service.");
+                        }
+                        telecomManager.addNewIncomingCall(
+                                PhoneAccountUtils.TEST_SELF_MANAGED_PHONE_ACCOUNT_HANDLE, extras);
+
+                        CtsConnectionService ctsConnectionService =
+                                CtsConnectionService.waitForAndGetConnectionService();
+                        if (ctsConnectionService == null) {
+                            mStep2Status.setImageResource(R.drawable.fs_error);
+                            return new Throwable("Could not get connection service.");
+                        }
+
+                        CtsConnection connection = ctsConnectionService.waitForAndGetConnection();
+                        if (connection == null) {
+                            mStep2Status.setImageResource(R.drawable.fs_error);
+                            return new Throwable("Could not get connection.");
+                        }
+                        // Wait until the connection knows its audio state changed; at this point
+                        // Telecom knows about the connection and can answer.
+                        connection.waitForAudioStateChanged();
+                        // Make it active to simulate an answer.
+                        connection.setActive();
+
+                        // Place the second call. It should trigger the incoming call UX.
+                        Bundle extras2 = new Bundle();
+                        extras2.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
+                                TEST_DIAL_NUMBER_2);
+                        telecomManager.addNewIncomingCall(
+                                PhoneAccountUtils.TEST_SELF_MANAGED_PHONE_ACCOUNT_HANDLE_2,
+                                extras2);
+
+                        return null;
+                    } catch (Throwable t) {
+                        return  t;
+                    }
+                }
+
+                @Override
+                protected void onPostExecute(Throwable t) {
+                    if (t == null) {
+                        mStep2Status.setImageResource(R.drawable.fs_good);
+                        mShowUi.setEnabled(false);
+                        mConfirm.setEnabled(true);
+                    } else {
+                        mStep2Status.setImageResource(R.drawable.fs_error);
+                    }
+                }
+            }).execute();
+
+
+        });
+
+        mStep3Status = view.findViewById(R.id.step_2_status);
+        mConfirm = view.findViewById(R.id.telecom_incoming_self_mgd_confirm_answer_button);
+        mConfirm.setOnClickListener(v -> {
+            CtsConnectionService ctsConnectionService = CtsConnectionService.getConnectionService();
+            if (ctsConnectionService == null) {
+                mStep3Status.setImageResource(R.drawable.fs_error);
+                return;
+            }
+            List<CtsConnection> connections = ctsConnectionService.getConnections();
+            if (connections.size() != 1) {
+                mStep3Status.setImageResource(R.drawable.fs_error);
+                return;
+            }
+
+            if (connections.get(0).getState() == Connection.STATE_ACTIVE) {
+                mStep3Status.setImageResource(R.drawable.fs_good);
+                getPassButton().setEnabled(true);
+            } else {
+                mStep3Status.setImageResource(R.drawable.fs_error);
+            }
+        });
+
+        mShowUi.setEnabled(false);
+        mConfirm.setEnabled(false);
+    }
+}