Merge "PhoneCapability cleanup"
diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java
old mode 100755
new mode 100644
index 835b2b4..0d6ef2c
--- a/src/java/com/android/internal/telephony/Connection.java
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -43,6 +43,8 @@
 public abstract class Connection {
     private static final String TAG = "Connection";
 
+    public static final String ADHOC_CONFERENCE_ADDRESS = "tel:conf-factory";
+
     public interface PostDialListener {
         void onPostDialWait();
         void onPostDialChar(char c);
@@ -185,6 +187,8 @@
     protected String mAddress;     // MAY BE NULL!!!
     @UnsupportedAppUsage
     protected String mDialString;          // outgoing calls only
+    protected String[] mParticipantsToDial;// outgoing calls only
+    protected boolean mIsAdhocConference;
     @UnsupportedAppUsage
     protected int mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
     @UnsupportedAppUsage
@@ -314,6 +318,20 @@
     }
 
     /**
+     * Gets the participants address (e.g. phone number) associated with connection.
+     *
+     * @return address or null if unavailable
+     */
+    public String[] getParticipantsToDial() {
+        return mParticipantsToDial;
+    }
+
+    // return whether connection is AdhocConference or not
+    public boolean isAdhocConference() {
+        return mIsAdhocConference;
+    }
+
+    /**
      * Gets CNAP name associated with connection.
      * @return cnap name or null if unavailable
      */
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index 1cc5a08..7edb451 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -213,12 +213,6 @@
     }
 
     @Override
-    public void notifyOemHookRawEventForSubscriber(Phone sender, byte[] rawData) {
-        mTelephonyRegistryMgr.notifyOemHookRawEventForSubscriber(sender.getSubId(),
-            sender.getPhoneId(), rawData);
-    }
-
-    @Override
     public void notifyPhoneCapabilityChanged(PhoneCapability capability) {
         mTelephonyRegistryMgr.notifyPhoneCapabilityChanged(capability);
     }
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 65310d0..85e69d0 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -1222,6 +1222,38 @@
                 ringingCallState.isAlive());
     }
 
+    private boolean useImsForCall(DialArgs dialArgs) {
+        return isImsUseEnabled()
+                && mImsPhone != null
+                && (mImsPhone.isVolteEnabled() || mImsPhone.isWifiCallingEnabled() ||
+                (mImsPhone.isVideoEnabled() && VideoProfile.isVideo(dialArgs.videoState)))
+                && (mImsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE);
+    }
+
+    @Override
+    public Connection startConference(String[] participantsToDial, DialArgs dialArgs)
+            throws CallStateException {
+        Phone imsPhone = mImsPhone;
+        boolean useImsForCall = useImsForCall(dialArgs);
+        logd("useImsForCall=" + useImsForCall);
+        if (useImsForCall) {
+            try {
+                if (DBG) logd("Trying IMS PS Conference call");
+                return imsPhone.startConference(participantsToDial, dialArgs);
+            } catch (CallStateException e) {
+                if (DBG) logd("IMS PS conference call exception " + e +
+                        "useImsForCall =" + useImsForCall + ", imsPhone =" + imsPhone);
+                 CallStateException ce = new CallStateException(e.getError(), e.getMessage());
+                 ce.setStackTrace(e.getStackTrace());
+                 throw ce;
+            }
+        } else {
+            throw new CallStateException(
+                CallStateException.ERROR_OUT_OF_SERVICE,
+                "cannot dial conference call in out of service");
+        }
+    }
+
     @Override
     public Connection dial(String dialString, @NonNull DialArgs dialArgs)
             throws CallStateException {
@@ -1242,11 +1274,7 @@
         boolean allowWpsOverIms = configManager.getConfigForSubId(getSubId())
                 .getBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL);
 
-        boolean useImsForCall = isImsUseEnabled()
-                 && imsPhone != null
-                 && (imsPhone.isVolteEnabled() || imsPhone.isWifiCallingEnabled() ||
-                 (imsPhone.isVideoEnabled() && VideoProfile.isVideo(dialArgs.videoState)))
-                 && (imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
+        boolean useImsForCall = useImsForCall(dialArgs)
                  && (isWpsCall ? allowWpsOverIms : true);
 
         boolean useImsForEmergency = imsPhone != null
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index b4a4042..e8bd32a 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -703,13 +703,7 @@
                 break;
 
             case EVENT_UNSOL_OEM_HOOK_RAW:
-                ar = (AsyncResult)msg.obj;
-                if (ar.exception == null) {
-                    byte[] data = (byte[])ar.result;
-                    mNotifier.notifyOemHookRawEventForSubscriber(this, data);
-                } else {
-                    Rlog.e(LOG_TAG, "OEM hook raw exception: " + ar.exception);
-                }
+                // deprecated, ignore
                 break;
 
             case EVENT_CONFIG_LCE:
diff --git a/src/java/com/android/internal/telephony/PhoneInternalInterface.java b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
index 4d94270..757cd8e 100644
--- a/src/java/com/android/internal/telephony/PhoneInternalInterface.java
+++ b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
@@ -438,6 +438,21 @@
     Connection dial(String dialString, @NonNull DialArgs dialArgs) throws CallStateException;
 
     /**
+     * Initiate a new conference connection. This happens asynchronously, so you
+     * cannot assume the audio path is connected (or a call index has been
+     * assigned) until PhoneStateChanged notification has occurred.
+     *
+     * @param participantsToDial The participants to dial.
+     * @param dialArgs Parameters to perform the start conference with.
+     * @exception CallStateException if a new outgoing call is not currently
+     *                possible because no more call slots exist or a call exists
+     *                that is dialing, alerting, ringing, or waiting. Other
+     *                errors are handled asynchronously.
+     */
+    Connection startConference(String[] participantsToDial, @NonNull DialArgs dialArgs)
+            throws CallStateException;
+
+    /**
      * Handles PIN MMI commands (PIN/PIN2/PUK/PUK2), which are initiated
      * without SEND (so <code>dial</code> is not appropriate).
      *
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index a762951..aa6c4bc 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -86,9 +86,6 @@
     /** Send a notification that the users mobile data setting has changed */
     void notifyUserMobileDataStateChanged(Phone sender, boolean state);
 
-    /** Send a notification with an OEM hook payload */
-    void notifyOemHookRawEventForSubscriber(Phone sender, byte[] rawData);
-
     /** Send a notification that the phone capability has changed */
     void notifyPhoneCapabilityChanged(PhoneCapability capability);
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index ba718e6..a363003 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -863,6 +863,18 @@
     }
 
     @Override
+    public Connection startConference(String[] participantsToDial, DialArgs dialArgs)
+            throws CallStateException {
+         ImsDialArgs.Builder imsDialArgsBuilder;
+         if (!(dialArgs instanceof ImsDialArgs)) {
+             imsDialArgsBuilder = ImsDialArgs.Builder.from(dialArgs);
+         } else {
+             imsDialArgsBuilder = ImsDialArgs.Builder.from((ImsDialArgs) dialArgs);
+         }
+         return mCT.startConference(participantsToDial, imsDialArgsBuilder.build());
+    }
+
+    @Override
     public Connection dial(String dialString, DialArgs dialArgs) throws CallStateException {
         return dialInternal(dialString, dialArgs, null);
     }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index da471ec..34994b8f 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -810,6 +810,91 @@
         }
     }
 
+    private boolean prepareForDialing(ImsPhone.ImsDialArgs dialArgs) throws CallStateException {
+        boolean holdBeforeDial = false;
+        // note that this triggers call state changed notif
+        clearDisconnected();
+        if (mImsManager == null) {
+            throw new CallStateException("service not available");
+        }
+        // See if there are any issues which preclude placing a call; throw a CallStateException
+        // if there is.
+        checkForDialIssues();
+        int videoState = dialArgs.videoState;
+        if (!canAddVideoCallDuringImsAudioCall(videoState)) {
+            throw new CallStateException("cannot dial in current state");
+        }
+
+        // The new call must be assigned to the foreground call.
+        // That call must be idle, so place anything that's
+        // there on hold
+        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
+            if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
+                //we should have failed in checkForDialIssues above before we get here
+                throw new CallStateException(CallStateException.ERROR_TOO_MANY_CALLS,
+                        "Already too many ongoing calls.");
+            }
+            // foreground call is empty for the newly dialed connection
+            holdBeforeDial = true;
+            mPendingCallVideoState = videoState;
+            mPendingIntentExtras = dialArgs.intentExtras;
+            holdActiveCallForPendingMo();
+        }
+
+        ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
+        ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
+
+        synchronized (mSyncHold) {
+            if (holdBeforeDial) {
+                fgState = mForegroundCall.getState();
+                bgState = mBackgroundCall.getState();
+                //holding foreground call failed
+                if (fgState == ImsPhoneCall.State.ACTIVE) {
+                    throw new CallStateException("cannot dial in current state");
+                }
+                //holding foreground call succeeded
+                if (bgState == ImsPhoneCall.State.HOLDING) {
+                    holdBeforeDial = false;
+                }
+            }
+        }
+        return holdBeforeDial;
+    }
+
+    public Connection startConference(String[] participantsToDial, ImsPhone.ImsDialArgs dialArgs)
+            throws CallStateException {
+
+        int clirMode = dialArgs.clirMode;
+        int videoState = dialArgs.videoState;
+
+        if (DBG) log("dial clirMode=" + clirMode);
+        boolean holdBeforeDial = prepareForDialing(dialArgs);
+
+        mClirMode = clirMode;
+
+        synchronized (mSyncHold) {
+            mLastDialArgs = dialArgs;
+            mPendingMO = new ImsPhoneConnection(mPhone,
+                    participantsToDial, this, mForegroundCall,
+                    false);
+            mPendingMO.setVideoState(videoState);
+            if (dialArgs.rttTextStream != null) {
+                log("startConference: setting RTT stream on mPendingMO");
+                mPendingMO.setCurrentRttTextStream(dialArgs.rttTextStream);
+            }
+        }
+        addConnection(mPendingMO);
+
+        if (!holdBeforeDial) {
+            dialInternal(mPendingMO, clirMode, videoState, dialArgs.intentExtras);
+        }
+
+        updatePhoneState();
+        mPhone.notifyPreciseCallStateChanged();
+
+        return mPendingMO;
+    }
+
     @UnsupportedAppUsage
     public Connection dial(String dialString, int videoState, Bundle intentExtras) throws
             CallStateException {
@@ -845,20 +930,8 @@
             dialString = convertNumberIfNecessary(mPhone, dialString);
         }
 
-        // note that this triggers call state changed notif
-        clearDisconnected();
-
-        if (mImsManager == null) {
-            throw new CallStateException("service not available");
-        }
-
-        // See if there are any issues which preclude placing a call; throw a CallStateException
-        // if there is.
-        checkForDialIssues();
-
-        if (!canAddVideoCallDuringImsAudioCall(videoState)) {
-            throw new CallStateException("cannot dial in current state");
-        }
+        mClirMode = clirMode;
+        boolean holdBeforeDial = prepareForDialing(dialArgs);
 
         if (isPhoneInEcmMode && isEmergencyNumber) {
             handleEcmTimer(ImsPhone.CANCEL_ECM_TIMER);
@@ -872,46 +945,10 @@
             videoState = VideoProfile.STATE_AUDIO_ONLY;
         }
 
-        boolean holdBeforeDial = false;
-
-        // The new call must be assigned to the foreground call.
-        // That call must be idle, so place anything that's
-        // there on hold
-        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
-            if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
-                //we should have failed in checkForDialIssues above before we get here
-                throw new CallStateException(CallStateException.ERROR_TOO_MANY_CALLS,
-                        "Already too many ongoing calls.");
-            }
-            // foreground call is empty for the newly dialed connection
-            holdBeforeDial = true;
-            // Cache the video state for pending MO call.
-            mPendingCallVideoState = videoState;
-            mPendingIntentExtras = dialArgs.intentExtras;
-            holdActiveCallForPendingMo();
-        }
-
-        ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
-        ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
-
-        mClirMode = clirMode;
+        // Cache the video state for pending MO call.
+        mPendingCallVideoState = videoState;
 
         synchronized (mSyncHold) {
-            if (holdBeforeDial) {
-                fgState = mForegroundCall.getState();
-                bgState = mBackgroundCall.getState();
-
-                //holding foreground call failed
-                if (fgState == ImsPhoneCall.State.ACTIVE) {
-                    throw new CallStateException("cannot dial in current state");
-                }
-
-                //holding foreground call succeeded
-                if (bgState == ImsPhoneCall.State.HOLDING) {
-                    holdBeforeDial = false;
-                }
-            }
-
             mLastDialString = dialString;
             mLastDialArgs = dialArgs;
             mPendingMO = new ImsPhoneConnection(mPhone,
@@ -1122,8 +1159,9 @@
             return;
         }
 
-        if (conn.getAddress()== null || conn.getAddress().length() == 0
-                || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
+        if (!conn.isAdhocConference() &&
+                (conn.getAddress()== null || conn.getAddress().length() == 0
+                || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0)) {
             // Phone number is invalid
             conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER);
             sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
@@ -1142,6 +1180,9 @@
         try {
             String[] callees = new String[] { conn.getAddress() };
             ImsCallProfile profile = mImsManager.createCallProfile(serviceType, callType);
+            if (conn.isAdhocConference()) {
+                profile.setCallExtraBoolean(ImsCallProfile.EXTRA_CONFERENCE, true);
+            }
             profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
 
             if (isEmergencyCall) {
@@ -1181,7 +1222,9 @@
                 // being sent to the lower layers/to the network.
             }
 
-            ImsCall imsCall = mImsManager.makeCall(profile, callees, mImsCallListener);
+            ImsCall imsCall = mImsManager.makeCall(profile,
+                    conn.isAdhocConference() ? conn.getParticipantsToDial() : callees,
+                    mImsCallListener);
             conn.setImsCall(imsCall);
 
             mMetrics.writeOnImsCallStart(mPhone.getPhoneId(),
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index 0a6c215..f87876d 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -186,6 +186,7 @@
         mHandler = new MyHandler(mOwner.getLooper());
         mHandlerMessenger = new Messenger(mHandler);
         mImsCall = imsCall;
+        mIsAdhocConference = isMultiparty();
 
         if ((imsCall != null) && (imsCall.getCallProfile() != null)) {
             mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI);
@@ -261,6 +262,37 @@
         }
     }
 
+    /** This is an MO conference call, created when dialing */
+    public ImsPhoneConnection(Phone phone, String[] participantsToDial, ImsPhoneCallTracker ct,
+            ImsPhoneCall parent, boolean isEmergency) {
+        super(PhoneConstants.PHONE_TYPE_IMS);
+        createWakeLock(phone.getContext());
+        acquireWakeLock();
+
+        mOwner = ct;
+        mHandler = new MyHandler(mOwner.getLooper());
+        mHandlerMessenger = new Messenger(mHandler);
+
+        mDialString = mAddress = Connection.ADHOC_CONFERENCE_ADDRESS;
+        mParticipantsToDial = participantsToDial;
+        mIsAdhocConference = true;
+
+        mIsIncoming = false;
+        mCnapName = null;
+        mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
+        mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
+        mCreateTime = System.currentTimeMillis();
+
+        mParent = parent;
+        parent.attachFake(this, ImsPhoneCall.State.DIALING);
+
+        if (phone.getContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_use_voip_mode_for_ims)) {
+            setAudioModeIsVoip(true);
+        }
+    }
+
+
     public void dispose() {
     }
 
@@ -1280,6 +1312,8 @@
         sb.append(getTelecomCallId());
         sb.append(" address: ");
         sb.append(Rlog.pii(LOG_TAG, getAddress()));
+        sb.append(" isAdhocConf: ");
+        sb.append(isAdhocConference() ? "Y" : "N");
         sb.append(" ImsCall: ");
         synchronized (this) {
             if (mImsCall == null) {
diff --git a/src/java/com/android/internal/telephony/sip/SipPhone.java b/src/java/com/android/internal/telephony/sip/SipPhone.java
index 72fd9da..8e85e9a 100644
--- a/src/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/src/java/com/android/internal/telephony/sip/SipPhone.java
@@ -186,6 +186,12 @@
     }
 
     @Override
+    public Connection startConference(String[] participantsToDial, DialArgs dialArgs)
+            throws CallStateException {
+        throw new CallStateException("startConference: not supported");
+    }
+
+    @Override
     public Connection dial(String dialString, DialArgs dialArgs) throws CallStateException {
         synchronized (SipPhone.class) {
             return dialInternal(dialString, dialArgs.videoState);