Merge "createConnection fix and misc cleanup" into lmp-dev
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index a1e9cea..ccd8941 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -708,9 +708,9 @@
     void abort() {
         if (mCreateConnectionProcessor != null) {
             mCreateConnectionProcessor.abort();
-        } else if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT ||
-                mState == CallState.CONNECTING) {
-            handleCreateConnectionFailure(DisconnectCause.LOCAL, null);
+        } else if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT
+                || mState == CallState.CONNECTING) {
+            handleCreateConnectionFailure(DisconnectCause.OUTGOING_CANCELED, null);
         } else {
             Log.v(this, "Cannot abort a call which isn't either PRE_DIAL_WAIT or CONNECTING");
         }
diff --git a/src/com/android/telecomm/CallActivity.java b/src/com/android/telecomm/CallActivity.java
index 10db104..89b89e6 100644
--- a/src/com/android/telecomm/CallActivity.java
+++ b/src/com/android/telecomm/CallActivity.java
@@ -117,7 +117,6 @@
      * @param intent Call intent containing data about the handle to call.
      */
     private void processOutgoingCallIntent(Intent intent) {
-
         String uriString = intent.getData().getSchemeSpecificPart();
         Uri handle = Uri.fromParts(
                 PhoneNumberUtils.isUriNumber(uriString) ? "sip" : "tel", uriString, null);
diff --git a/src/com/android/telecomm/CallLogManager.java b/src/com/android/telecomm/CallLogManager.java
index d0668b9..ee65255 100644
--- a/src/com/android/telecomm/CallLogManager.java
+++ b/src/com/android/telecomm/CallLogManager.java
@@ -23,6 +23,7 @@
 import android.telecomm.CallState;
 import android.telecomm.PhoneAccountHandle;
 import android.telecomm.VideoProfile;
+import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 
 import com.android.internal.telephony.CallerInfo;
@@ -88,8 +89,9 @@
 
     @Override
     public void onCallStateChanged(Call call, int oldState, int newState) {
-        if ((newState == CallState.DISCONNECTED || newState == CallState.ABORTED) &&
-                oldState != CallState.PRE_DIAL_WAIT) {
+        if ((newState == CallState.DISCONNECTED || newState == CallState.ABORTED)
+                && oldState != CallState.PRE_DIAL_WAIT
+                && call.getDisconnectCause() != DisconnectCause.OUTGOING_CANCELED) {
             int type;
             if (!call.isIncoming()) {
                 type = Calls.OUTGOING_TYPE;
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 80ef854..a9c2c3e 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -250,8 +250,8 @@
     /**
      * Starts the process to attach the call to a connection service.
      *
-     * @param phoneAccountHandle The phone account which contains the component name of the connection
-     *                     service to use for this call.
+     * @param phoneAccountHandle The phone account which contains the component name of the
+     *        connection service to use for this call.
      * @param extras The optional extras Bundle passed with the intent used for the incoming call.
      */
     void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
@@ -273,7 +273,6 @@
         call.startCreateConnection();
     }
 
-
     /**
      * Kicks off the first steps to creating an outgoing call so that InCallUI can launch.
      *
@@ -281,10 +280,9 @@
      * placeOutgoingCall directly.
      *
      * @param handle Handle to connect the call with.
-     * @param phoneAccountHandle The phone account which contains the component name of the connection
-     *                     service to use for this call.
-     * @param extras The optional extras Bundle passed with the intent used for the outgoing call.
-     *
+     * @param phoneAccountHandle The phone account which contains the component name of the
+     *        connection service to use for this call.
+     * @param extras The optional extras Bundle passed with the intent used for the incoming call.
      */
     Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) {
         // We only allow a single outgoing call at any given time. Before placing a call, make sure
@@ -296,6 +294,27 @@
             return null;
         }
 
+        TelecommApp app = TelecommApp.getInstance();
+
+        // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call
+        // as if a phoneAccount was not specified (does the default behavior instead).
+        if (phoneAccountHandle != null) {
+            List<PhoneAccountHandle> enabledAccounts =
+                    app.getPhoneAccountRegistrar().getOutgoingPhoneAccounts();
+            if (!enabledAccounts.contains(phoneAccountHandle)) {
+                phoneAccountHandle = null;
+            }
+        }
+
+        if (phoneAccountHandle == null) {
+            // No preset account, check if default exists
+            PhoneAccountHandle defaultAccountHandle =
+                    app.getPhoneAccountRegistrar().getDefaultOutgoingPhoneAccount();
+            if (defaultAccountHandle != null) {
+                phoneAccountHandle = defaultAccountHandle;
+            }
+        }
+
         // Create a call with original handle. The handle may be changed when the call is attached
         // to a connection service, but in most cases will remain the same.
         call = new Call(
@@ -307,7 +326,14 @@
                 false /* isIncoming */,
                 false /* isConference */);
         call.setExtras(extras);
-        call.setState(CallState.CONNECTING);
+
+        final boolean emergencyCall = TelephonyUtil.shouldProcessAsEmergency(app, call.getHandle());
+        if (phoneAccountHandle == null && !emergencyCall) {
+            // This is the state where the user is expected to select an account
+            call.setState(CallState.PRE_DIAL_WAIT);
+        } else {
+            call.setState(CallState.CONNECTING);
+        }
 
         if (!isPotentialMMICode(handle)) {
             addCall(call);
@@ -327,15 +353,14 @@
      * @param speakerphoneOn Whether or not to turn the speakerphone on once the call connects.
      * @param videoState The desired video state for the outgoing call.
      */
-    void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo,
-            PhoneAccountHandle accountHandle, boolean speakerphoneOn, int videoState) {
+    void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn,
+            int videoState) {
         if (call == null) {
             // don't do anything if the call no longer exists
             Log.i(this, "Canceling unknown call.");
             return;
         }
 
-        TelecommApp app = TelecommApp.getInstance();
         final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayHandle();
 
         if (gatewayInfo == null) {
@@ -345,49 +370,22 @@
                     Log.pii(uriHandle), Log.pii(handle));
         }
 
-        //TODO: phone account is already set in {@link #startOutgoingCall}, refactor so this is
-        // not redundant.
-        //
-        // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call
-        // as if a phoneAccount was not specified (does the default behavior instead).
-        if (accountHandle != null) {
-            List<PhoneAccountHandle> enabledAccounts =
-                    app.getPhoneAccountRegistrar().getOutgoingPhoneAccounts();
-            if (!enabledAccounts.contains(accountHandle)) {
-                accountHandle = null;
-            }
-        }
-
         call.setHandle(uriHandle);
         call.setGatewayInfo(gatewayInfo);
         call.setStartWithSpeakerphoneOn(speakerphoneOn);
         call.setVideoState(videoState);
 
-        // This block of code will attempt to pre-determine a phone account
+        TelecommApp app = TelecommApp.getInstance();
         final boolean emergencyCall = TelephonyUtil.shouldProcessAsEmergency(app, call.getHandle());
         if (emergencyCall) {
             // Emergency -- CreateConnectionProcessor will choose accounts automatically
             call.setTargetPhoneAccount(null);
-        } else if (accountHandle != null) {
-            // NOTE: this is currently not an option because no feature currently exists to
-            // preset a phone account
-            Log.d(this, "CALL with phone account: " + accountHandle);
-            call.setTargetPhoneAccount(accountHandle);
-        } else {
-            // No preset account, check if default exists
-            PhoneAccountHandle defaultAccountHandle =
-                    app.getPhoneAccountRegistrar().getDefaultOutgoingPhoneAccount();
-            if (defaultAccountHandle != null) {
-                call.setTargetPhoneAccount(defaultAccountHandle);
-            }
         }
 
         if (call.getTargetPhoneAccount() != null || emergencyCall) {
-            // If the account is selected, proceed to place the outgoing call
+            // If the account has been set, proceed to place the outgoing call.
+            // Otherwise the connection will be initiated when the account is set by the user.
             call.startCreateConnection();
-        } else {
-            // This is the state where the user is expected to select an account
-            setCallState(call, CallState.PRE_DIAL_WAIT);
         }
     }
 
@@ -515,6 +513,7 @@
         }
     }
 
+
     /**
      * Instructs Telecomm to put the specified call on hold. Intended to be invoked by the
      * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
@@ -755,6 +754,7 @@
         return call;
     }
 
+
     /**
      * Adds the specified call to the main list of live calls.
      *
diff --git a/src/com/android/telecomm/ConnectionServiceRepository.java b/src/com/android/telecomm/ConnectionServiceRepository.java
index ec2c90e..efd1ba4 100644
--- a/src/com/android/telecomm/ConnectionServiceRepository.java
+++ b/src/com/android/telecomm/ConnectionServiceRepository.java
@@ -38,21 +38,6 @@
     ConnectionServiceRepository() {
     }
 
-    Collection<ConnectionServiceWrapper> lookupServices() {
-        PackageManager packageManager = TelecommApp.getInstance().getPackageManager();
-        Intent intent = new Intent(ConnectionService.SERVICE_INTERFACE);
-        ArrayList<ConnectionServiceWrapper> services = new ArrayList<>();
-
-        for (ResolveInfo entry : packageManager.queryIntentServices(intent, 0)) {
-            ServiceInfo serviceInfo = entry.serviceInfo;
-            if (serviceInfo != null) {
-                services.add(getService(new ComponentName(
-                        serviceInfo.packageName, serviceInfo.name)));
-            }
-        }
-        return services;
-    }
-
     ConnectionServiceWrapper getService(ComponentName componentName) {
         ConnectionServiceWrapper service = mServiceCache.get(componentName);
         if (service == null) {
diff --git a/src/com/android/telecomm/CreateConnectionProcessor.java b/src/com/android/telecomm/CreateConnectionProcessor.java
index 3e6e544..aacb80b 100644
--- a/src/com/android/telecomm/CreateConnectionProcessor.java
+++ b/src/com/android/telecomm/CreateConnectionProcessor.java
@@ -107,9 +107,32 @@
 
     private void attemptNextPhoneAccount() {
         Log.v(this, "attemptNextPhoneAccount");
+        PhoneAccountRegistrar registrar = TelecommApp.getInstance().getPhoneAccountRegistrar();
+        CallAttemptRecord attempt = null;
+        if (mAttemptRecordIterator.hasNext()) {
+            attempt = mAttemptRecordIterator.next();
 
-        if (mResponse != null && mAttemptRecordIterator.hasNext()) {
-            CallAttemptRecord attempt = mAttemptRecordIterator.next();
+            if (!registrar.phoneAccountHasPermission(attempt.connectionManagerPhoneAccount)) {
+                Log.w(this,
+                        "Connection mgr does not have BIND_CONNECTION_SERVICE for attempt: %s",
+                        attempt);
+                attemptNextPhoneAccount();
+                return;
+            }
+
+            // If the target PhoneAccount differs from the ConnectionManager phone acount, ensure it
+            // also has BIND_CONNECTION_SERVICE permission.
+            if (!attempt.connectionManagerPhoneAccount.equals(attempt.targetPhoneAccount) &&
+                    !registrar.phoneAccountHasPermission(attempt.targetPhoneAccount)) {
+                Log.w(this,
+                        "Target PhoneAccount does not have BIND_CONNECTION_SERVICE for attempt: %s",
+                        attempt);
+                attemptNextPhoneAccount();
+                return;
+            }
+        }
+
+        if (mResponse != null && attempt != null) {
             Log.i(this, "Trying attempt %s", attempt);
             ConnectionServiceWrapper service =
                     mRepository.getService(
diff --git a/src/com/android/telecomm/NewOutgoingCallIntentBroadcaster.java b/src/com/android/telecomm/NewOutgoingCallIntentBroadcaster.java
index 65c1309..a45f4ef 100644
--- a/src/com/android/telecomm/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/telecomm/NewOutgoingCallIntentBroadcaster.java
@@ -24,7 +24,6 @@
 import android.net.Uri;
 import android.os.UserHandle;
 import android.telecomm.GatewayInfo;
-import android.telecomm.PhoneAccountHandle;
 import android.telecomm.TelecommManager;
 import android.telecomm.VideoProfile;
 import android.telephony.PhoneNumberUtils;
@@ -133,8 +132,7 @@
             }
 
             GatewayInfo gatewayInfo = getGateWayInfoFromIntent(intent, resultHandleUri);
-            PhoneAccountHandle accountHandle = getAccountHandleFromIntent(intent);
-            mCallsManager.placeOutgoingCall(mCall, resultHandleUri, gatewayInfo, accountHandle,
+            mCallsManager.placeOutgoingCall(mCall, resultHandleUri, gatewayInfo,
                     mIntent.getBooleanExtra(TelecommManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
                             false),
                     mIntent.getIntExtra(TelecommManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
@@ -215,7 +213,7 @@
             int videoState = mIntent.getIntExtra(
                     TelecommManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                     VideoProfile.VideoState.AUDIO_ONLY);
-            mCallsManager.placeOutgoingCall(mCall, Uri.fromParts(scheme, handle, null), null, null,
+            mCallsManager.placeOutgoingCall(mCall, Uri.fromParts(scheme, handle, null), null,
                     speakerphoneOn, videoState);
 
             // Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast
@@ -285,12 +283,6 @@
             Log.d(this, "Found and copied gateway provider extras to broadcast intent.");
             return;
         }
-        PhoneAccountHandle extraAccountHandle =
-                src.getParcelableExtra(TelecommManager.EXTRA_PHONE_ACCOUNT_HANDLE);
-        if (extraAccountHandle != null) {
-            dst.putExtra(TelecommManager.EXTRA_PHONE_ACCOUNT_HANDLE, extraAccountHandle);
-            Log.d(this, "Found and copied account extra to broadcast intent.");
-        }
 
         Log.d(this, "No provider extras found in call intent.");
     }
@@ -335,20 +327,6 @@
         return null;
     }
 
-    /**
-     * Extracts account/connection provider information from a provided intent..
-     *
-     * @param intent to extract account information from.
-     * @return Account object containing extracted account information
-     */
-    public static PhoneAccountHandle getAccountHandleFromIntent(Intent intent) {
-        if (intent == null) {
-            return null;
-        }
-
-        return intent.getParcelableExtra(TelecommManager.EXTRA_PHONE_ACCOUNT_HANDLE);
-    }
-
     private void launchSystemDialer(Context context, Uri handle) {
         Intent systemDialerIntent = new Intent();
         final Resources resources = context.getResources();
diff --git a/src/com/android/telecomm/PhoneAccountRegistrar.java b/src/com/android/telecomm/PhoneAccountRegistrar.java
index 71eb95b..f3a5a0b 100644
--- a/src/com/android/telecomm/PhoneAccountRegistrar.java
+++ b/src/com/android/telecomm/PhoneAccountRegistrar.java
@@ -16,9 +16,11 @@
 
 package com.android.telecomm;
 
+import android.Manifest;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.telecomm.ConnectionService;
 import android.telecomm.PhoneAccount;
 import android.telecomm.PhoneAccountHandle;
@@ -45,6 +47,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.SecurityException;
 import java.lang.String;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -231,6 +234,15 @@
     // TODO: Should we implement an artificial limit for # of accounts associated with a single
     // ComponentName?
     public void registerPhoneAccount(PhoneAccount account) {
+        // Enforce the requirement that a connection service for a phone account has the correct
+        // permission.
+        if (!phoneAccountHasPermission(account.getAccountHandle())) {
+            Log.w(this, "Phone account %s does not have BIND_CONNECTION_SERVICE permission.",
+                    account.getAccountHandle());
+            throw new SecurityException(
+                    "PhoneAccount connection service requires BIND_CONNECTION_SERVICE permission.");
+        }
+
         mState.accounts.add(account);
         // Search for duplicates and remove any that are found.
         for (int i = 0; i < mState.accounts.size() - 1; i++) {
@@ -311,6 +323,27 @@
         }
     }
 
+    /**
+     * Determines if the connection service specified by a {@link PhoneAccountHandle} has the
+     * {@link Manifest.permission#BIND_CONNECTION_SERVICE} permission.
+     *
+     * @param phoneAccountHandle The phone account to check.
+     * @return {@code True} if the phone account has permission.
+     */
+    public boolean phoneAccountHasPermission(PhoneAccountHandle phoneAccountHandle) {
+        PackageManager packageManager = TelecommApp.getInstance().getPackageManager();
+        try {
+            ServiceInfo serviceInfo = packageManager.getServiceInfo(
+                    phoneAccountHandle.getComponentName(), 0);
+
+            return serviceInfo.permission != null &&
+                    serviceInfo.permission.equals(Manifest.permission.BIND_CONNECTION_SERVICE);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(this, "Name not found %s", e);
+            return false;
+        }
+    }
+
     ////////////////////////////////////////////////////////////////////////////////////////////////
 
     // TODO: Add a corresponding has(...) method to class PhoneAccount itself and remove this one
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index c17803f..d8877fb 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -29,13 +29,15 @@
 
         <!-- Miscellaneous telecomm app-related test activities. -->
 
-        <service android:name="com.android.telecomm.testapps.TestConnectionService">
+        <service android:name="com.android.telecomm.testapps.TestConnectionService"
+                 android:permission="android.permission.BIND_CONNECTION_SERVICE" >
             <intent-filter>
                 <action android:name="android.telecomm.ConnectionService" />
             </intent-filter>
         </service>
 
-        <service android:name="com.android.telecomm.testapps.TestConnectionManager">
+        <service android:name="com.android.telecomm.testapps.TestConnectionManager"
+                 android:permission="android.permission.BIND_CONNECTION_SERVICE" >
             <intent-filter>
                 <action android:name="android.telecomm.ConnectionService" />
             </intent-filter>