Merge "Change subId to int from long" into lmp-mr1-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e7a7781..88e90ae 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -38,13 +38,6 @@
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
     <uses-permission android:name="android.permission.BROADCAST_CALLLOG_INFO" />
 
-    <!-- Protects the ability to register any PhoneAccount with a capability flags of either
-         PhoneAccount#CAPABILITY_CALL_PROVIDER or PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION. -->
-    <permission
-            android:name="com.android.server.telecom.permission.REGISTER_PROVIDER_OR_SUBSCRIPTION"
-            android:label="Register CALL_PROVIDER or SIM_SUBSCRIPTION PhoneAccount"
-            android:protectionLevel="signature"/>
-
     <permission
             android:name="android.permission.BROADCAST_CALLLOG_INFO"
             android:label="Broadcast the call type/duration information"
diff --git a/src/com/android/server/telecom/BluetoothPhoneService.java b/src/com/android/server/telecom/BluetoothPhoneService.java
index 51dbbdf..8bf50ae 100644
--- a/src/com/android/server/telecom/BluetoothPhoneService.java
+++ b/src/com/android/server/telecom/BluetoothPhoneService.java
@@ -329,16 +329,63 @@
 
         @Override
         public void onCallStateChanged(Call call, int oldState, int newState) {
+            // If a call is being put on hold because of a new connecting call, ignore the
+            // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
+            // state atomically.
+            // When the call later transitions to DIALING/DISCONNECTED we will then send out the
+            // aggregated update.
+            if (oldState == CallState.ACTIVE && newState == CallState.ON_HOLD) {
+                for (Call otherCall : CallsManager.getInstance().getCalls()) {
+                    if (otherCall.getState() == CallState.CONNECTING) {
+                        return;
+                    }
+                }
+            }
+
+            // To have an active call and another dialing at the same time is an invalid BT
+            // state. We can assume that the active call will be automatically held which will
+            // send another update at which point we will be in the right state.
+            if (CallsManager.getInstance().getActiveCall() != null
+                    && oldState == CallState.CONNECTING && newState == CallState.DIALING) {
+                return;
+            }
             updateHeadsetWithCallState(false /* force */);
         }
 
         @Override
         public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
-            updateHeadsetWithCallState(false /* force */);
+            // The BluetoothPhoneService does not need to respond to changes in foreground calls,
+            // which are always accompanied by call state changes anyway.
         }
 
         @Override
         public void onIsConferencedChanged(Call call) {
+            /*
+             * Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
+             * because conference change events are not atomic and multiple callbacks get fired
+             * when two calls are conferenced together. This confuses updateHeadsetWithCallState
+             * if it runs in the middle of two calls being conferenced and can cause spurious and
+             * incorrect headset state updates. One of the scenarios is described below for CDMA
+             * conference calls.
+             *
+             * 1) Call 1 and Call 2 are being merged into conference Call 3.
+             * 2) Call 1 has its parent set to Call 3, but Call 2 does not have a parent yet.
+             * 3) updateHeadsetWithCallState now thinks that there are two active calls (Call 2 and
+             * Call 3) when there is actually only one active call (Call 3).
+             */
+            if (call.getParentCall() != null) {
+                // If this call is newly conferenced, ignore the callback. We only care about the
+                // one sent for the parent conference call.
+                Log.d(this, "Ignoring onIsConferenceChanged from child call with new parent");
+                return;
+            }
+            if (call.getChildCalls().size() == 1) {
+                // If this is a parent call with only one child, ignore the callback as well since
+                // the minimum number of child calls to start a conference call is 2. We expect
+                // this to be called again when the parent call has another child call added.
+                Log.d(this, "Ignoring onIsConferenceChanged from parent with only one child call");
+                return;
+            }
             updateHeadsetWithCallState(false /* force */);
         }
     };
@@ -649,6 +696,7 @@
         // For conference calls which support swapping the active call within the conference
         // (namely CDMA calls) we need to expose that as a held call in order for the BT device
         // to show "swap" and "merge" functionality.
+        boolean ignoreHeldCallChange = false;
         if (activeCall != null && activeCall.isConference()) {
             if (activeCall.can(PhoneCapabilities.SWAP_CONFERENCE)) {
                 // Indicate that BT device should show SWAP command by indicating that there is a
@@ -657,6 +705,16 @@
             } else if (activeCall.can(PhoneCapabilities.MERGE_CONFERENCE)) {
                 numHeldCalls = 1;  // Merge is available, so expose via numHeldCalls.
             }
+
+            for (Call childCall : activeCall.getChildCalls()) {
+                // Held call has changed due to it being combined into a CDMA conference. Keep
+                // track of this and ignore any future update since it doesn't really count as
+                // a call change.
+                if (mOldHeldCall == childCall) {
+                    ignoreHeldCallChange = true;
+                    break;
+                }
+            }
         }
 
         if (mBluetoothHeadset != null &&
@@ -665,7 +723,7 @@
                  bluetoothCallState != mBluetoothCallState ||
                  !TextUtils.equals(ringingAddress, mRingingAddress) ||
                  ringingAddressType != mRingingAddressType ||
-                 heldCall != mOldHeldCall ||
+                 (heldCall != mOldHeldCall && !ignoreHeldCallChange) ||
                  force)) {
 
             // If the call is transitioning into the alerting state, send DIALING first.
@@ -682,7 +740,18 @@
             mRingingAddressType = ringingAddressType;
 
             if (sendDialingFirst) {
-                Log.i(TAG, "Sending dialing state");
+                // Log in full to make logs easier to debug.
+                Log.i(TAG, "updateHeadsetWithCallState " +
+                        "numActive %s, " +
+                        "numHeld %s, " +
+                        "callState %s, " +
+                        "ringing number %s, " +
+                        "ringing type %s",
+                        mNumActiveCalls,
+                        mNumHeldCalls,
+                        CALL_STATE_DIALING,
+                        Log.pii(mRingingAddress),
+                        mRingingAddressType);
                 mBluetoothHeadset.phoneStateChanged(
                         mNumActiveCalls,
                         mNumHeldCalls,
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index eb6b745..3b6192a 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -29,6 +29,7 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.net.Uri;
@@ -206,6 +207,14 @@
                 return;
             }
 
+            if (getPhoneAccount(accountHandle).hasCapabilities(
+                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+                // If the account selected is a SIM account, propagate down to the subscription
+                // record.
+                long subId = getSubscriptionIdForPhoneAccount(accountHandle);
+                SubscriptionManager.setDefaultVoiceSubId(subId);
+            }
+
             mState.defaultOutgoing = accountHandle;
         }
 
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 5df44c4..95a7e75 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -51,7 +51,9 @@
  */
 public class TelecomServiceImpl extends ITelecomService.Stub {
     private static final String REGISTER_PROVIDER_OR_SUBSCRIPTION =
-            "com.android.server.telecom.permission.REGISTER_PROVIDER_OR_SUBSCRIPTION";
+            android.Manifest.permission.REGISTER_PROVIDER_OR_SUBSCRIPTION;
+    private static final String REGISTER_CONNECTION_MANAGER =
+            android.Manifest.permission.REGISTER_CONNECTION_MANAGER;
 
     /** The context. */
     private Context mContext;
@@ -305,6 +307,9 @@
                 account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
                 enforceRegisterProviderOrSubscriptionPermission();
             }
+            if (account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
+                enforceRegisterConnectionManagerPermission();
+            }
 
             mPhoneAccountRegistrar.registerPhoneAccount(account);
         } catch (Exception e) {
@@ -596,6 +601,10 @@
         enforcePermission(REGISTER_PROVIDER_OR_SUBSCRIPTION);
     }
 
+    private void enforceRegisterConnectionManagerPermission() {
+        enforcePermission(REGISTER_CONNECTION_MANAGER);
+    }
+
     private void enforceReadPermission() {
         enforcePermission(Manifest.permission.READ_PHONE_STATE);
     }