Self Managed CS implementation.

Implementation for self-managed connection service APIs.

Add ability for self-managed CS to:
- register a phone account and have it be auto-enabled.
- add ability for self-managed calls, ensuring only self-manage calls
can be added.

Add implementations for new isXCallPermitted APIs.
Filter self-managed calls from InCallServices.
Ensure self managed connection creation doesn't use emergency call CS.
Add ability to set audio route from self-managed connection
Overhaul some of the CallsManager getCallsWithState methods to support
other use cases for self-managed calls (also use steams/filters.. Ooo aah)

Test: Manual
Bug: 34159263
Change-Id: I3131fd48ee5c5aa36c0e88992fa51879af07d495
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index c1eb258..6c7202b 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -25,6 +25,7 @@
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
     <uses-permission android:name="android.permission.READ_CALL_LOG" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
@@ -155,6 +156,24 @@
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
+          </activity>
+
+        <activity android:name="com.android.server.telecom.testapps.SelfManagedCallingActivity"
+                  android:label="@string/selfManagedCallingActivityLabel"
+                  android:process="com.android.server.telecom.testapps.SelfMangingCallingApp">
+          <intent-filter>
+              <action android:name="android.intent.action.MAIN" />
+              <category android:name="android.intent.category.DEFAULT" />
+              <category android:name="android.intent.category.LAUNCHER" />
+          </intent-filter>
         </activity>
+
+        <service android:name="com.android.server.telecom.testapps.SelfManagedConnectionService"
+                 android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+                 android:process="com.android.server.telecom.testapps.SelfMangingCallingApp">
+          <intent-filter>
+              <action android:name="android.telecom.ConnectionService" />
+          </intent-filter>
+        </service>
     </application>
 </manifest>
diff --git a/testapps/res/layout/self_managed_call_list_item.xml b/testapps/res/layout/self_managed_call_list_item.xml
new file mode 100644
index 0000000..f3be4ad
--- /dev/null
+++ b/testapps/res/layout/self_managed_call_list_item.xml
@@ -0,0 +1,71 @@
+<?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="wrap_content">
+    <TextView
+        android:id="@+id/phoneNumber"
+        android:layout_gravity="left"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="25dp"
+        android:text="TextView" />
+    <TextView
+        android:id="@+id/callState"
+        android:layout_gravity="left"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="25dp"
+        android:text="TextView" />
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Active"
+            android:id="@+id/setActiveButton" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Hold"
+            android:id="@+id/setHeldButton" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Disconnect"
+            android:id="@+id/disconnectButton" />
+    </LinearLayout>
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Speaker"
+            android:id="@+id/speakerButton" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Earpiece"
+            android:id="@+id/earpieceButton" />
+    </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
new file mode 100644
index 0000000..f239997
--- /dev/null
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -0,0 +1,81 @@
+<?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="fill_parent"
+    android:layout_height="fill_parent">
+    <CheckBox
+        android:id="@+id/checkIfPermittedBeforeCalling"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/checkIfPermittedBeforeCallingButton" />
+    <TableLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+        <TableRow android:layout_span="2">
+            <RadioGroup>
+                <RadioButton
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Acct 1"
+                    android:id="@+id/useAcct1Button"/>
+                <RadioButton
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="Acct 2"
+                    android:id="@+id/useAcct2Button"/>
+            </RadioGroup>
+        </TableRow>
+        <TableRow android:layout_span="2">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Place call:" />
+        </TableRow>
+        <TableRow>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Number:" />
+            <EditText
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:id="@+id/phoneNumber" />
+        </TableRow>
+        <TableRow>
+            <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Outgoing Call"
+                android:id="@+id/placeOutgoingCallButton" />
+            <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Incoming Call"
+                android:id="@+id/placeIncomingCallButton" />
+        </TableRow>
+        <TableRow android:layout_span="2">
+            <ListView
+                android:id="@+id/callList"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:listSelector="@null"
+                android:divider="@null" />
+        </TableRow>
+    </TableLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/testapps/res/raw/sample_audio.ogg b/testapps/res/raw/sample_audio.ogg
new file mode 100644
index 0000000..0129b46
--- /dev/null
+++ b/testapps/res/raw/sample_audio.ogg
Binary files differ
diff --git a/testapps/res/raw/sample_audio2.ogg b/testapps/res/raw/sample_audio2.ogg
new file mode 100644
index 0000000..a0b39b4
--- /dev/null
+++ b/testapps/res/raw/sample_audio2.ogg
Binary files differ
diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml
index 83d1ef3..74a3fd4 100644
--- a/testapps/res/values/donottranslate_strings.xml
+++ b/testapps/res/values/donottranslate_strings.xml
@@ -49,4 +49,17 @@
     <string name="UssdUiAppLabel">Test Ussd UI</string>
 
     <string name="placeUssdButton">Send USSD</string>
+
+    <!-- String for button in SelfManagedCallingActivity. -->
+    <string name="checkIfPermittedBeforeCallingButton">Check if calls permitted before calling</string>
+
+    <string name="selfManagedCallingActivityLabel">Self-Managed Sample</string>
+
+    <string name="outgoingCallNotPermitted">Outgoing call not permitted.</string>
+
+    <string name="outgoingCallNotPermittedCS">Outgoing call not permitted (CS Reported).</string>
+
+    <string name="incomingCallNotPermitted">Incoming call not permitted.</string>
+
+    <string name="incomingCallNotPermittedCS">Incoming call not permitted (CS Reported).</string>
 </resources>
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
new file mode 100644
index 0000000..e0d7442
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -0,0 +1,133 @@
+/*
+ * 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.server.telecom.testapps;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.telecom.ConnectionRequest;
+import android.telecom.Log;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Manages the list of {@link SelfManagedConnection} active in the sample third-party calling app.
+ */
+public class SelfManagedCallList {
+    public abstract static class Listener {
+        public void onCreateIncomingConnectionFailed(ConnectionRequest request) {};
+        public void onCreateOutgoingConnectionFailed(ConnectionRequest request) {};
+        public void onConnectionListChanged() {};
+    }
+
+    public static String SELF_MANAGED_ACCOUNT_1 = "1";
+    public static String SELF_MANAGED_ACCOUNT_2 = "2";
+
+    private static SelfManagedCallList sInstance;
+    private static ComponentName COMPONENT_NAME = new ComponentName(
+            SelfManagedCallList.class.getPackage().getName(),
+            SelfManagedConnectionService.class.getName());
+    private static Uri SELF_MANAGED_ADDRESS_1 = Uri.fromParts(PhoneAccount.SCHEME_TEL, "555-1212",
+            "");
+    private static Uri SELF_MANAGED_ADDRESS_2 = Uri.fromParts(PhoneAccount.SCHEME_SIP,
+            "me@test.org", "");
+    private static Map<String, PhoneAccountHandle> mPhoneAccounts = new ArrayMap();
+
+    public static SelfManagedCallList getInstance() {
+        if (sInstance == null) {
+            sInstance = new SelfManagedCallList();
+        }
+        return sInstance;
+    }
+
+    private Listener mListener;
+
+    private List<SelfManagedConnection> mConnections = new ArrayList<>();
+
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    public void registerPhoneAccounts(Context context) {
+        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1, SELF_MANAGED_ADDRESS_1);
+        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_2, SELF_MANAGED_ADDRESS_2);
+    }
+
+    public void registerPhoneAccount(Context context, String id, Uri address) {
+        PhoneAccountHandle handle = new PhoneAccountHandle(COMPONENT_NAME, id);
+        mPhoneAccounts.put(id, handle);
+        PhoneAccount.Builder builder = PhoneAccount.builder(handle, id)
+                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+                .addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
+                .setAddress(address)
+                .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+                        PhoneAccount.CAPABILITY_VIDEO_CALLING |
+                        PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING);
+
+        TelecomManager.from(context).registerPhoneAccount(builder.build());
+    }
+
+    public PhoneAccountHandle getPhoneAccountHandle(String id) {
+        return mPhoneAccounts.get(id);
+    }
+
+    public void notifyCreateIncomingConnectionFailed(ConnectionRequest request) {
+        if (mListener != null) {
+            mListener.onCreateIncomingConnectionFailed(request);
+        }
+    }
+
+    public void notifyCreateOutgoingConnectionFailed(ConnectionRequest request) {
+        if (mListener != null) {
+            mListener.onCreateOutgoingConnectionFailed(request);
+        }
+    }
+
+    public void addConnection(SelfManagedConnection connection) {
+        Log.i(this, "addConnection %s", connection);
+        mConnections.add(connection);
+        if (mListener != null) {
+            Log.i(this, "addConnection calling onConnectionListChanged %s", connection);
+            mListener.onConnectionListChanged();
+        }
+    }
+
+    public void removeConnection(SelfManagedConnection connection) {
+        Log.i(this, "removeConnection %s", connection);
+        mConnections.remove(connection);
+        if (mListener != null) {
+            Log.i(this, "removeConnection calling onConnectionListChanged %s", connection);
+            mListener.onConnectionListChanged();
+        }
+    }
+
+    public List<SelfManagedConnection> getConnections() {
+        return mConnections;
+    }
+
+    public void notifyCallModified() {
+        if (mListener != null) {
+            mListener.onConnectionListChanged();
+        }
+    }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
new file mode 100644
index 0000000..29d3c5f
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
@@ -0,0 +1,189 @@
+/*
+ * 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.server.telecom.testapps;
+
+import android.telecom.CallAudioState;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import java.util.List;
+
+public class SelfManagedCallListAdapter extends BaseAdapter {
+
+    private static final String TAG = "SelfMgCallListAd";
+    /**
+     * Listener used to handle tap of the "disconnect" button for a connection.
+     */
+    private View.OnClickListener mDisconnectListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            View parent = (View) v.getParent().getParent();
+            SelfManagedConnection connection = (SelfManagedConnection) parent.getTag();
+            connection.setConnectionDisconnected(DisconnectCause.LOCAL);
+            SelfManagedCallList.getInstance().removeConnection(connection);
+        }
+    };
+
+    /**
+     * Listener used to handle tap of the "active" button for a connection.
+     */
+    private View.OnClickListener mActiveListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            View parent = (View) v.getParent().getParent();
+            SelfManagedConnection connection = (SelfManagedConnection) parent.getTag();
+            connection.setConnectionActive();
+            notifyDataSetChanged();
+        }
+    };
+
+    /**
+     * Listener used to handle tap of the "held" button for a connection.
+     */
+    private View.OnClickListener mHeldListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            View parent = (View) v.getParent().getParent();
+            SelfManagedConnection connection = (SelfManagedConnection) parent.getTag();
+            connection.setConnectionHeld();
+            notifyDataSetChanged();
+        }
+    };
+
+    private View.OnClickListener mSpeakerListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            View parent = (View) v.getParent().getParent();
+            SelfManagedConnection connection = (SelfManagedConnection) parent.getTag();
+            connection.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
+            notifyDataSetChanged();
+        }
+    };
+
+    private View.OnClickListener mEarpieceListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            View parent = (View) v.getParent().getParent();
+            SelfManagedConnection connection = (SelfManagedConnection) parent.getTag();
+            connection.setAudioRoute(CallAudioState.ROUTE_EARPIECE);
+            notifyDataSetChanged();
+        }
+    };
+
+    private final LayoutInflater mLayoutInflater;
+
+    private List<SelfManagedConnection> mConnections;
+
+    public SelfManagedCallListAdapter(LayoutInflater layoutInflater,
+                                      List<SelfManagedConnection> connections) {
+
+        mLayoutInflater = layoutInflater;
+        mConnections = connections;
+    }
+
+    @Override
+    public int getCount() {
+        return mConnections.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return mConnections.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        View result = convertView == null
+                ? mLayoutInflater.inflate(R.layout.self_managed_call_list_item, parent, false)
+                : convertView;
+        SelfManagedConnection connection = mConnections.get(position);
+        PhoneAccountHandle phoneAccountHandle = connection.getExtras().getParcelable(
+                SelfManagedConnection.EXTRA_PHONE_ACCOUNT_HANDLE);
+        CallAudioState audioState = connection.getCallAudioState();
+        String audioRoute = "?";
+        if (audioState != null) {
+            switch (audioState.getRoute()) {
+                case CallAudioState.ROUTE_BLUETOOTH:
+                    audioRoute = "BT";
+                    break;
+                case CallAudioState.ROUTE_SPEAKER:
+                    audioRoute = "\uD83D\uDD0A";
+                    break;
+                case CallAudioState.ROUTE_EARPIECE:
+                    audioRoute = "\uD83D\uDC42";
+                    break;
+                case CallAudioState.ROUTE_WIRED_HEADSET:
+                    audioRoute = "\uD83D\uDD0C";
+                    break;
+                default:
+                    audioRoute = "?";
+                    break;
+            }
+        }
+        String callType;
+        if (connection.isIncomingCall()) {
+            if (connection.isIncomingCallUiShowing()) {
+                callType = "Incoming - Show Incoming UX";
+            } else {
+                callType = "Incoming - DO NOT SHOW Incoming UX";
+            }
+        } else {
+            callType = "Outgoing";
+        }
+        setInfoForRow(result, phoneAccountHandle.getId(), connection.getAddress().toString(),
+                android.telecom.Connection.stateToString(connection.getState()), audioRoute,
+                callType);
+        result.setTag(connection);
+        return result;
+    }
+
+    public void updateConnections() {
+        Log.i(TAG, "updateConnections "+ mConnections.size());
+
+        notifyDataSetChanged();
+    }
+
+    private void setInfoForRow(View view, String accountName, String number,
+                               String status, String audioRoute, String callType) {
+
+        TextView numberTextView = (TextView) view.findViewById(R.id.phoneNumber);
+        TextView statusTextView = (TextView) view.findViewById(R.id.callState);
+        View activeButton = view.findViewById(R.id.setActiveButton);
+        activeButton.setOnClickListener(mActiveListener);
+        View disconnectButton = view.findViewById(R.id.disconnectButton);
+        disconnectButton.setOnClickListener(mDisconnectListener);
+        View setHeldButton = view.findViewById(R.id.setHeldButton);
+        setHeldButton.setOnClickListener(mHeldListener);
+        View speakerButton = view.findViewById(R.id.speakerButton);
+        speakerButton.setOnClickListener(mSpeakerListener);
+        View earpieceButton = view.findViewById(R.id.earpieceButton);
+        earpieceButton.setOnClickListener(mEarpieceListener);
+        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
new file mode 100644
index 0000000..8b7eae0
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -0,0 +1,150 @@
+/*
+ * 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.server.telecom.testapps;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telecom.ConnectionRequest;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.RadioButton;
+import android.widget.Toast;
+
+import com.android.server.telecom.testapps.R;
+
+import java.util.Objects;
+
+/**
+ * Provides a sample third-party calling app UX which implements the self managed connection service
+ * APIs.
+ */
+public class SelfManagedCallingActivity extends Activity {
+    private static final String TAG = "SelfMgCallActivity";
+    private SelfManagedCallList mCallList = SelfManagedCallList.getInstance();
+    private CheckBox mCheckIfPermittedBeforeCalling;
+    private Button mPlaceOutgoingCallButton;
+    private Button mPlaceIncomingCallButton;
+    private RadioButton mUseAcct1Button;
+    private RadioButton mUseAcct2Button;
+    private EditText mNumber;
+    private ListView mListView;
+    private SelfManagedCallListAdapter mListAdapter;
+
+    private SelfManagedCallList.Listener mCallListListener = new SelfManagedCallList.Listener() {
+        @Override
+        public void onCreateIncomingConnectionFailed(ConnectionRequest request) {
+            Log.i(TAG, "onCreateIncomingConnectionFailed " + request);
+            Toast.makeText(SelfManagedCallingActivity.this,
+                    R.string.incomingCallNotPermittedCS , Toast.LENGTH_SHORT).show();
+        };
+
+        @Override
+        public void onCreateOutgoingConnectionFailed(ConnectionRequest request) {
+            Log.i(TAG, "onCreateOutgoingConnectionFailed " + request);
+            Toast.makeText(SelfManagedCallingActivity.this,
+                    R.string.outgoingCallNotPermittedCS , Toast.LENGTH_SHORT).show();
+        };
+
+        @Override
+        public void onConnectionListChanged() {
+            Log.i(TAG, "onConnectionListChanged");
+            mListAdapter.updateConnections();
+        };
+    };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.self_managed_sample_main);
+        mCheckIfPermittedBeforeCalling = (CheckBox) findViewById(
+                R.id.checkIfPermittedBeforeCalling);
+        mPlaceOutgoingCallButton = (Button) findViewById(R.id.placeOutgoingCallButton);
+        mPlaceOutgoingCallButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                placeOutgoingCall();
+            }
+        });
+        mPlaceIncomingCallButton = (Button) findViewById(R.id.placeIncomingCallButton);
+        mPlaceIncomingCallButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                placeIncomingCall();
+            }
+        });
+        mUseAcct1Button = (RadioButton) findViewById(R.id.useAcct1Button);
+        mUseAcct2Button = (RadioButton) findViewById(R.id.useAcct2Button);
+        mNumber = (EditText) findViewById(R.id.phoneNumber);
+        mListView = (ListView) findViewById(R.id.callList);
+        mCallList.setListener(mCallListListener);
+        mCallList.registerPhoneAccounts(this);
+        mListAdapter = new SelfManagedCallListAdapter(getLayoutInflater(),
+                mCallList.getConnections());
+        mListView.setAdapter(mListAdapter);
+        Log.i(TAG, "onCreate - mCallList id " + Objects.hashCode(mCallList));
+    }
+
+    private PhoneAccountHandle getSelectedPhoneAccountHandle() {
+        if (mUseAcct1Button.isChecked()) {
+            return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_1);
+        } else if (mUseAcct2Button.isChecked()) {
+            return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_2);
+        }
+        return null;
+    }
+
+    private void placeOutgoingCall() {
+        TelecomManager tm = TelecomManager.from(this);
+        PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
+
+        if (mCheckIfPermittedBeforeCalling.isChecked()) {
+            if (!tm.isOutgoingCallPermitted(phoneAccountHandle)) {
+                Toast.makeText(this, R.string.outgoingCallNotPermitted , Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                getSelectedPhoneAccountHandle());
+        tm.placeCall(Uri.parse(mNumber.getText().toString()), extras);
+    }
+
+    private void placeIncomingCall() {
+        TelecomManager tm = TelecomManager.from(this);
+        PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
+
+        if (mCheckIfPermittedBeforeCalling.isChecked()) {
+            if (!tm.isIncomingCallPermitted(phoneAccountHandle)) {
+                Toast.makeText(this, R.string.incomingCallNotPermitted , Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
+                Uri.parse(mNumber.getText().toString()));
+        tm.addNewIncomingCall(getSelectedPhoneAccountHandle(), extras);
+    }
+}
\ No newline at end of file
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
new file mode 100644
index 0000000..5fd8eba
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
@@ -0,0 +1,89 @@
+/*
+ * 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.server.telecom.testapps;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.telecom.CallAudioState;
+import android.telecom.Connection;
+import android.telecom.ConnectionService;
+import android.telecom.DisconnectCause;
+
+/**
+ * Sample self-managed {@link Connection} for a self-managed {@link ConnectionService}.
+ * <p>
+ * See {@link android.telecom} for more information on self-managed {@link ConnectionService}s.
+ */
+public class SelfManagedConnection extends Connection {
+    public static final String EXTRA_PHONE_ACCOUNT_HANDLE =
+            "com.android.server.telecom.testapps.extra.PHONE_ACCOUNT_HANDLE";
+
+    private final SelfManagedCallList mCallList;
+    private final MediaPlayer mMediaPlayer;
+    private final boolean mIsIncomingCall;
+    private boolean mIsIncomingCallUiShowing;
+
+    SelfManagedConnection(SelfManagedCallList callList, Context context, boolean isIncoming) {
+        mCallList = callList;
+        mMediaPlayer = createMediaPlayer(context);
+        mIsIncomingCall = isIncoming;
+    }
+
+    /**
+     * Handles updates to the audio state of the connection.
+     * @param state The new connection audio state.
+     */
+    @Override
+    public void onCallAudioStateChanged(CallAudioState state) {
+        mCallList.notifyCallModified();
+    }
+
+    public void setConnectionActive() {
+        mMediaPlayer.start();
+        setActive();
+    }
+
+    public void setConnectionHeld() {
+        mMediaPlayer.pause();
+        setOnHold();
+    }
+
+    public void setConnectionDisconnected(int cause) {
+        mMediaPlayer.stop();
+        setDisconnected(new DisconnectCause(cause));
+        destroy();
+    }
+
+    public void setIsIncomingCallUiShowing(boolean showing) {
+        mIsIncomingCallUiShowing = showing;
+    }
+
+    public boolean isIncomingCallUiShowing() {
+        return mIsIncomingCallUiShowing;
+    }
+
+    public boolean isIncomingCall() {
+        return mIsIncomingCall;
+    }
+
+    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);
+        mediaPlayer.setLooping(true);
+        return mediaPlayer;
+    }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
new file mode 100644
index 0000000..31bbcd6
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -0,0 +1,83 @@
+/*
+ * 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.server.telecom.testapps;
+
+import android.os.Bundle;
+import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.ConnectionService;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+import java.util.Objects;
+
+/**
+ * Sample implementation of the self-managed {@link ConnectionService} API.
+ * <p>
+ * See {@link android.telecom} for more information on self-managed {@link ConnectionService}s.
+ */
+public class SelfManagedConnectionService extends ConnectionService {
+    private final SelfManagedCallList mCallList = SelfManagedCallList.getInstance();
+
+    @Override
+    public Connection onCreateOutgoingConnection(
+            PhoneAccountHandle connectionManagerAccount,
+            final ConnectionRequest request) {
+
+        return createSelfManagedConnection(request, false);
+    }
+
+    @Override
+    public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request) {
+        return createSelfManagedConnection(request, true);
+    }
+
+    @Override
+    public void onCreateIncomingConnectionFailed(ConnectionRequest request) {
+        mCallList.notifyCreateIncomingConnectionFailed(request);
+    }
+
+    @Override
+    public void onCreateOutgoingConnectionFailed(ConnectionRequest request) {
+        mCallList.notifyCreateOutgoingConnectionFailed(request);
+    }
+
+
+    private Connection createSelfManagedConnection(ConnectionRequest request, boolean isIncoming) {
+        SelfManagedConnection connection = new SelfManagedConnection(mCallList,
+                getApplicationContext(), isIncoming);
+        connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
+        connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
+        connection.setExtras(request.getExtras());
+        if (isIncoming) {
+            connection.setIsIncomingCallUiShowing(request.shouldShowIncomingCallUi());
+        }
+
+        // Track the phone account handle which created this connection so we can distinguish them
+        // in the sample call list later.
+        Bundle moreExtras = new Bundle();
+        moreExtras.putParcelable(SelfManagedConnection.EXTRA_PHONE_ACCOUNT_HANDLE,
+                request.getAccountHandle());
+        connection.putExtras(moreExtras);
+        connection.setVideoState(request.getVideoState());
+        Log.i(this, "createSelfManagedConnection %s", connection);
+        mCallList.addConnection(connection);
+        return connection;
+    }
+}