ISipService: add new open(), open3(), getListOfProfiles()

replacing old openToReceiveCalls(), and broadcast intents when profiles are
added and removed during those calls.

Change-Id: I507327e7a043de3aa3256b88d74199a83a7b1438
diff --git a/demo/com/android/sip/demo/SipCallSetup.java b/demo/com/android/sip/demo/SipCallSetup.java
index 0585b61..a4058ab 100644
--- a/demo/com/android/sip/demo/SipCallSetup.java
+++ b/demo/com/android/sip/demo/SipCallSetup.java
@@ -244,8 +244,8 @@
                 mSipManager.register(myself.mProfile, 3600,
                         createRegistrationListener());
             } else {
-                mSipManager.openToReceiveCalls(myself.mProfile,
-                        INCOMING_CALL_ACTION, createRegistrationListener());
+                mSipManager.open(myself.mProfile, INCOMING_CALL_ACTION,
+                        createRegistrationListener());
             }
         } catch (Exception e) {
             Log.e(TAG, "register()", e);
diff --git a/settings/com/android/settings/sip/SipAutoRegistration.java b/settings/com/android/settings/sip/SipAutoRegistration.java
index 4230150..39c7b83 100644
--- a/settings/com/android/settings/sip/SipAutoRegistration.java
+++ b/settings/com/android/settings/sip/SipAutoRegistration.java
@@ -60,7 +60,7 @@
                         + SipSettings.PROFILES_DIR);
                 for (SipProfile profile : sipProfileList) {
                     try {
-                        sipManager.openToReceiveCalls(profile,
+                        sipManager.open(profile,
                                 SipSettings.INCOMING_CALL_ACTION, null);
                     } catch (SipException e) {
                         Log.e(TAG, "failed" + profile.getProfileName(), e);
diff --git a/settings/com/android/settings/sip/SipSettings.java b/settings/com/android/settings/sip/SipSettings.java
index 679c55f..b7c26ba 100644
--- a/settings/com/android/settings/sip/SipSettings.java
+++ b/settings/com/android/settings/sip/SipSettings.java
@@ -298,7 +298,7 @@
     private void registerProfile(SipProfile profile) {
         if (profile != null) {
             try {
-                mSipManager.openToReceiveCalls(profile, INCOMING_CALL_ACTION,
+                mSipManager.open(profile, INCOMING_CALL_ACTION,
                         createRegistrationListener());
             } catch (Exception e) {
                 Log.e(TAG, "register failed", e);
diff --git a/src/android/net/sip/ISipService.aidl b/src/android/net/sip/ISipService.aidl
index 99af890..6c68213 100644
--- a/src/android/net/sip/ISipService.aidl
+++ b/src/android/net/sip/ISipService.aidl
@@ -24,7 +24,8 @@
  * {@hide}
  */
 interface ISipService {
-    void openToReceiveCalls(in SipProfile localProfile,
+    void open(in SipProfile localProfile);
+    void open3(in SipProfile localProfile,
             String incomingCallBroadcastAction,
             in ISipSessionListener listener);
     void close(in String localProfileUri);
@@ -36,4 +37,6 @@
     ISipSession createSession(in SipProfile localProfile,
             in ISipSessionListener listener);
     ISipSession getPendingSession(String callId);
+
+    SipProfile[] getListOfProfiles();
 }
diff --git a/src/android/net/sip/SipManager.java b/src/android/net/sip/SipManager.java
index 2971c0f..6d48e65 100644
--- a/src/android/net/sip/SipManager.java
+++ b/src/android/net/sip/SipManager.java
@@ -23,6 +23,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 
+import java.text.ParseException;
 import javax.sip.SipException;
 
 /**
@@ -31,10 +32,10 @@
  * <ul>
  * <li>register a {@link SipProfile} to have the background SIP service listen
  *      to incoming calls and broadcast them with registered command string. See
- *      {@link #openToReceiveCalls(SipProfile, String, SipRegistrationListener)},
- *      {@link #close(String)}, {@link #isOpened(String)} and
- *      {@link isRegistered(String)}. It also facilitates handling of the
- *      incoming call broadcast intent. See
+ *      {@link #open(SipProfile, String, SipRegistrationListener)},
+ *      {@link #open(SipProfile)}, {@link #close(String)},
+ *      {@link #isOpened(String)} and {@link isRegistered(String)}. It also
+ *      facilitates handling of the incoming call broadcast intent. See
  *      {@link #isIncomingCallIntent(Intent)}, {@link #getCallId(Intent)},
  *      {@link #getOfferSessionDescription(Intent)} and
  *      {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener)}.</li>
@@ -50,6 +51,18 @@
  * @hide
  */
 public class SipManager {
+    /** @hide */
+    public static final String SIP_INCOMING_CALL_ACTION =
+            "com.android.phone.SIP_INCOMING_CALL";
+    /** @hide */
+    public static final String SIP_ADD_PHONE_ACTION =
+            "com.android.phone.SIP_ADD_PHONE";
+    /** @hide */
+    public static final String SIP_REMOVE_PHONE_ACTION =
+            "com.android.phone.SIP_REMOVE_PHONE";
+    /** @hide */
+    public static final String LOCAL_URI_KEY = "LOCAL SIPURI";
+
     private static final String CALL_ID_KEY = "CallID";
     private static final String OFFER_SD_KEY = "OfferSD";
 
@@ -94,9 +107,34 @@
     }
 
     /**
-     * Enables to receive incoming calls for the specified SIP profile. When
-     * enabled, the SIP service will register the profile to the corresponding
-     * server periodically in order to receive calls from the server.
+     * Opens the profile for making calls and/or receiving calls. Subsequent
+     * SIP calls can be made through the default phone UI. The caller may also
+     * make subsequent calls through
+     * {@link #makeAudioCall(Context, String, String, SipAudioCall.Listener)}.
+     * If the receiving-call option is enabled in the profile, the SIP service
+     * will register the profile to the corresponding server periodically in
+     * order to receive calls from the server.
+     *
+     * @param localProfile the SIP profile to make calls from
+     * @throws SipException if the profile contains incorrect settings or
+     *      calling the SIP service results in an error
+     */
+    public void open(SipProfile localProfile) throws SipException {
+        try {
+            mSipService.open(localProfile);
+        } catch (RemoteException e) {
+            throw new SipException("open()", e);
+        }
+    }
+
+    /**
+     * Opens the profile for making calls and/or receiving calls. Subsequent
+     * SIP calls can be made through the default phone UI. The caller may also
+     * make subsequent calls through
+     * {@link #makeAudioCall(Context, String, String, SipAudioCall.Listener)}.
+     * If the receiving-call option is enabled in the profile, the SIP service
+     * will register the profile to the corresponding server periodically in
+     * order to receive calls from the server.
      *
      * @param localProfile the SIP profile to receive incoming calls for
      * @param incomingCallBroadcastAction the action to be broadcast when an
@@ -105,21 +143,22 @@
      * @throws SipException if the profile contains incorrect settings or
      *      calling the SIP service results in an error
      */
-    public void openToReceiveCalls(SipProfile localProfile,
+    public void open(SipProfile localProfile,
             String incomingCallBroadcastAction,
             SipRegistrationListener listener) throws SipException {
         try {
-            mSipService.openToReceiveCalls(localProfile,
-                    incomingCallBroadcastAction, createRelay(listener));
+            mSipService.open3(localProfile, incomingCallBroadcastAction,
+                    createRelay(listener));
         } catch (RemoteException e) {
-            throw new SipException("openToReceiveCalls()", e);
+            throw new SipException("open()", e);
         }
     }
 
     /**
      * Sets the listener to listen to registration events. No effect if the
      * profile has not been opened to receive calls
-     * (see {@link #openToReceiveCalls(SipProfile, String, SipRegistrationListener)}).
+     * (see {@link #open(SipProfile, String, SipRegistrationListener)} and
+     * {@link #open(SipProfile)}).
      *
      * @param localProfileUri the URI of the profile
      * @param listener to listen to registration events; can be null
@@ -136,7 +175,7 @@
     }
 
     /**
-     * Closes to not receive calls for the specified profile. All the resources
+     * Closes the specified profile to not make/receive calls. All the resources
      * that were allocated to the profile are also released.
      *
      * @param localProfileUri the URI of the profile to close
@@ -202,6 +241,30 @@
     }
 
     /**
+     * Creates a {@link SipAudioCall} to make a call. To use this method, one
+     * must call {@link #open(SipProfile)} first.
+     *
+     * @param context context to create a {@link SipAudioCall} object
+     * @param localProfileUri URI of the SIP profile to make the call from
+     * @param peerProfileUri URI of the SIP profile to make the call to
+     * @param listener to listen to the call events from {@link SipAudioCall};
+     *      can be null
+     * @return a {@link SipAudioCall} object
+     * @throws SipException if calling the SIP service results in an error
+     */
+    public SipAudioCall makeAudioCall(Context context, String localProfileUri,
+            String peerProfileUri, SipAudioCall.Listener listener)
+            throws SipException {
+        try {
+            return makeAudioCall(context,
+                    new SipProfile.Builder(localProfileUri).build(),
+                    new SipProfile.Builder(peerProfileUri).build(), listener);
+        } catch (ParseException e) {
+            throw new SipException("build SipProfile", e);
+        }
+    }
+
+    /**
      * The method calls {@code takeAudioCall(context, incomingCallIntent,
      * listener, true}.
      *
@@ -312,9 +375,9 @@
 
     /**
      * Registers the profile to the corresponding server for receiving calls.
-     * {@link #openToReceiveCalls(SipProfile, String, SipRegistrationListener)}
-     * is still needed to be called at least once in order for the SIP service
-     * to broadcast an intent when an incoming call is received.
+     * {@link #open(SipProfile, String, SipRegistrationListener)} is still
+     * needed to be called at least once in order for the SIP service to
+     * broadcast an intent when an incoming call is received.
      *
      * @param localProfile the SIP profile to register with
      * @param expiryTime registration expiration time (in second)
@@ -333,13 +396,10 @@
     }
 
     /**
-     * Registers the profile to the corresponding server for receiving calls.
-     * {@link #openToReceiveCalls(SipProfile, String, SipRegistrationListener)}
-     * is still needed to be called at least once in order for the SIP service
-     * to broadcast an intent when an incoming call is received.
+     * Unregisters the profile from the corresponding server for not receiving
+     * further calls.
      *
      * @param localProfile the SIP profile to register with
-     * @param expiryTime registration expiration time (in second)
      * @param listener to listen to the registration events
      * @throws SipException if calling the SIP service results in an error
      */
@@ -396,6 +456,19 @@
         }
     }
 
+    /**
+     * Gets the list of profiles hosted by the SIP service. The user information
+     * (username, password and display name) are crossed out.
+     * @hide
+     */
+    public SipProfile[] getListOfProfiles() {
+        try {
+            return mSipService.getListOfProfiles();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
     private static class ListenerRelay extends SipSessionAdapter {
         private SipRegistrationListener mListener;
 
diff --git a/src/com/android/sip/SipServiceImpl.java b/src/com/android/sip/SipServiceImpl.java
index 18c5af7..c878da5 100644
--- a/src/com/android/sip/SipServiceImpl.java
+++ b/src/com/android/sip/SipServiceImpl.java
@@ -33,6 +33,7 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.IOException;
@@ -80,16 +81,69 @@
         mTimer = new WakeupTimer(context);
     }
 
-    public synchronized void openToReceiveCalls(SipProfile localProfile,
-            String incomingCallBroadcastAction,
-            ISipSessionListener listener) {
-        FLog.d(TAG, "openToReceiveCalls: " + localProfile.getUriString() + ": "
+    public synchronized SipProfile[] getListOfProfiles() {
+        SipProfile[] profiles = new SipProfile[mSipGroups.size()];
+        int i = 0;
+        for (SipSessionGroupExt group : mSipGroups.values()) {
+            profiles[i++] = duplicate(group.getLocalProfile());
+        }
+        return profiles;
+    }
+
+    private SipProfile duplicate(SipProfile p) {
+        try {
+            return new SipProfile.Builder(p.getUserName(), p.getSipDomain())
+                    .setProfileName(p.getProfileName())
+                    .setPassword("*")
+                    .setPort(p.getPort())
+                    .setProtocol(p.getProtocol())
+                    .setOutboundProxy(p.getProxyAddress())
+                    .setSendKeepAlive(p.getSendKeepAlive())
+                    .setAutoRegistration(p.getAutoRegistration())
+                    .setDisplayName("*")
+                    .build();
+        } catch (Exception e) {
+            Log.wtf(TAG, "duplicate()", e);
+            return null;
+        }
+    }
+
+    public void open(SipProfile localProfile) {
+        if (localProfile.getAutoRegistration()) {
+            openToReceiveCalls(localProfile);
+        } else {
+            openToMakeCalls(localProfile);
+        }
+    }
+
+    private void openToMakeCalls(SipProfile localProfile) {
+        try {
+            createGroup(localProfile);
+        } catch (SipException e) {
+            FLog.e(TAG, "openToMakeCalls()", e);
+            // TODO: how to send the exception back
+        }
+    }
+
+    private void openToReceiveCalls(SipProfile localProfile) {
+        open3(localProfile, SipManager.SIP_INCOMING_CALL_ACTION, null);
+    }
+
+    public synchronized void open3(SipProfile localProfile,
+            String incomingCallBroadcastAction, ISipSessionListener listener) {
+        if (TextUtils.isEmpty(incomingCallBroadcastAction)) {
+            throw new RuntimeException(
+                    "empty broadcast action for incoming call");
+        }
+        FLog.d(TAG, "open3: " + localProfile.getUriString() + ": "
                 + incomingCallBroadcastAction + ": " + listener);
         try {
             SipSessionGroupExt group = createGroup(localProfile,
                     incomingCallBroadcastAction, listener);
-            group.openToReceiveCalls();
-            if (isWifiOn()) grabWifiLock();
+            if (localProfile.getAutoRegistration()) {
+                group.openToReceiveCalls();
+                if (isWifiOn()) grabWifiLock();
+            }
         } catch (SipException e) {
             FLog.e(TAG, "openToReceiveCalls()", e);
             // TODO: how to send the exception back
@@ -99,6 +153,7 @@
     public synchronized void close(String localProfileUri) {
         SipSessionGroupExt group = mSipGroups.remove(localProfileUri);
         if (group != null) {
+            notifyProfileRemoved(group.getLocalProfile());
             group.closeToNotReceiveCalls();
             if (isWifiOn() && !anyOpened()) releaseWifiLock();
         }
@@ -156,6 +211,7 @@
         if (group == null) {
             group = new SipSessionGroupExt(localProfile, null, null);
             mSipGroups.put(key, group);
+            notifyProfileAdded(localProfile);
         }
         return group;
     }
@@ -173,10 +229,25 @@
             group = new SipSessionGroupExt(localProfile,
                     incomingCallBroadcastAction, listener);
             mSipGroups.put(key, group);
+            notifyProfileAdded(localProfile);
         }
         return group;
     }
 
+    private void notifyProfileAdded(SipProfile localProfile) {
+        Log.d(TAG, "notify: profile added: " + localProfile);
+        Intent intent = new Intent(SipManager.SIP_ADD_PHONE_ACTION);
+        intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString());
+        mContext.sendBroadcast(intent);
+    }
+
+    private void notifyProfileRemoved(SipProfile localProfile) {
+        Log.d(TAG, "notify: profile removed: " + localProfile);
+        Intent intent = new Intent(SipManager.SIP_REMOVE_PHONE_ACTION);
+        intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString());
+        mContext.sendBroadcast(intent);
+    }
+
     private boolean anyOpened() {
         for (SipSessionGroupExt group : mSipGroups.values()) {
             if (group.isOpened()) return true;
@@ -275,6 +346,10 @@
             mAutoRegistration.setListener(listener);
         }
 
+        public SipProfile getLocalProfile() {
+            return mSipGroup.getLocalProfile();
+        }
+
         // network connectivity is tricky because network can be disconnected
         // at any instant so need to deal with exceptions carefully even when
         // you think you are connected