Add conference call support to sl4a.

Implement InCallService for sl4a.
Add our InCallService to manifest.
Add conference call and Phone class related RPC calls to TelecomManagerFacade.

Change-Id: I2cdd25797e788d42e907082e2d12dbade29f052d
diff --git a/Common/src/com/googlecode/android_scripting/facade/tele/InCallServiceImpl.java b/Common/src/com/googlecode/android_scripting/facade/tele/InCallServiceImpl.java
new file mode 100644
index 0000000..1cae047
--- /dev/null
+++ b/Common/src/com/googlecode/android_scripting/facade/tele/InCallServiceImpl.java
@@ -0,0 +1,49 @@
+
+package com.googlecode.android_scripting.facade.tele;
+
+import java.util.HashMap;
+
+import android.telecom.Call;
+import android.telecom.InCallService;
+import android.telecom.Phone;
+
+import com.googlecode.android_scripting.Log;
+
+public class InCallServiceImpl extends InCallService {
+
+    public static Phone mPhone;
+    public static HashMap<String, Call> mCalls = new HashMap<String, Call>();
+
+    private Phone.Listener mPhoneListener = new Phone.Listener() {
+
+        @Override
+        public void onCallAdded(Phone phone, Call call) {
+            Log.d("onCallAdded: " + call.toString());
+            String id = TelecomManagerFacade.getCallId(call);
+            Log.d("Adding " + id);
+            mCalls.put(id, call);
+        }
+
+        @Override
+        public void onCallRemoved(Phone phone, Call call) {
+            Log.d("onCallRemoved: " + call.toString());
+            String id = TelecomManagerFacade.getCallId(call);
+            Log.d("Removing " + id);
+            mCalls.remove(id);
+        }
+    };
+
+    @Override
+    public void onPhoneCreated(Phone phone) {
+        Log.d("onPhoneCreated");
+        mPhone = phone;
+        mPhone.addListener(mPhoneListener);
+    }
+
+    @Override
+    public void onPhoneDestroyed(Phone phone) {
+        Log.d("onPhoneDestroyed");
+        mPhone.removeListener(mPhoneListener);
+        mPhone = null;
+    }
+}
diff --git a/Common/src/com/googlecode/android_scripting/facade/tele/TelecomManagerFacade.java b/Common/src/com/googlecode/android_scripting/facade/tele/TelecomManagerFacade.java
index 02edac2..a78610e 100644
--- a/Common/src/com/googlecode/android_scripting/facade/tele/TelecomManagerFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/tele/TelecomManagerFacade.java
@@ -16,17 +16,22 @@
 
 package com.googlecode.android_scripting.facade.tele;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import android.app.Service;
+import android.telecom.AudioState;
+import android.telecom.Call;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
 
+import com.googlecode.android_scripting.Log;
 import com.googlecode.android_scripting.facade.FacadeManager;
 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
 import com.googlecode.android_scripting.rpc.Rpc;
+import com.googlecode.android_scripting.rpc.RpcParameter;
 
 /**
  * Exposes TelecomManager functionality.
@@ -47,6 +52,27 @@
     public void shutdown() {
     }
 
+    /**
+     * Returns an identifier of the call. When a phone number is available, the number will be
+     * returned. Otherwise, the standard object toString result of the Call object. e.g. A
+     * conference call does not have a single number associated with it, thus the toString Id will
+     * be returned.
+     *
+     * @param call
+     * @return
+     */
+    public static String getCallId(Call call) {
+        try {
+            String handle = call.getDetails().getHandle().toString();
+            int idx = handle.indexOf(":");
+            String number = handle.substring(idx + 1).trim();
+            return number;
+        } catch (NullPointerException e) {
+            Log.d("Failed to get a number from the call object, using toString.");
+            return call.toString();
+        }
+    }
+
     @Rpc(description = "If there's a ringing call, accept on behalf of the user.")
     public void telecomAcceptRingingCall() {
         mTelecomManager.acceptRingingCall();
@@ -124,6 +150,53 @@
         return mTelecomManager.isRinging();
     }
 
+    @Rpc(description = "Joins two calls into a conference call. Calls are identified by their IDs listed by telecomPhoneGetCallIds")
+    public void telecomJoinCallsInConf(@RpcParameter(name = "callIdOne") String callIdOne,
+            @RpcParameter(name = "callIdTwo") String callIdTwo) {
+        Call callOne = InCallServiceImpl.mCalls.get(callIdOne);
+        Call callTwo = InCallServiceImpl.mCalls.get(callIdTwo);
+        callOne.conference(callTwo);
+    }
+
+    @Rpc(description = "Lists the IDs (phone numbers or hex hashes) of the current calls.")
+    public ArrayList<String> telecomPhoneGetCallIds() {
+        ArrayList<String> ids = new ArrayList<String>();
+        for (Call call : InCallServiceImpl.mPhone.getCalls()) {
+            ids.add(getCallId(call));
+        }
+        return ids;
+    }
+
+    @Rpc(description = "Sets the audio route (SPEAKER, BLUETOOTH, etc...).")
+    public void telecomPhoneSetAudioRoute(@RpcParameter(name = "route") String route) {
+        int r = 0;
+        if (route == "BLUETOOTH") {
+            r = AudioState.ROUTE_BLUETOOTH;
+        } else if (route == "EARPIECE") {
+            r = AudioState.ROUTE_EARPIECE;
+        } else if (route == "SPEAKER") {
+            r = AudioState.ROUTE_SPEAKER;
+        } else if (route == "WIRED_HEADSET") {
+            r = AudioState.ROUTE_WIRED_HEADSET;
+        } else if (route == "ALL") {
+            r = AudioState.ROUTE_ALL;
+        } else if (route == "WIRED_OR_EARPIECE") {
+            r = AudioState.ROUTE_WIRED_OR_EARPIECE;
+        }
+        InCallServiceImpl.mPhone.setAudioRoute(r);
+    }
+
+    @Rpc(description = "Turns the proximity sensor off. If screenOnImmediately is true, the screen will be turned on immediately")
+    public void telecomPhoneSetProximitySensorOff(
+            @RpcParameter(name = "screenOnImmediately") Boolean screenOnImmediately) {
+        InCallServiceImpl.mPhone.setProximitySensorOff(screenOnImmediately);
+    }
+
+    @Rpc(description = "Obtains the current call audio state of the phone.")
+    public AudioState telecomPhoneGetAudioState() {
+        return InCallServiceImpl.mPhone.getAudioState();
+    }
+
     @Rpc(description = "Silences the rigner if there's a ringing call.")
     public void telecomSilenceRinger() {
         mTelecomManager.silenceRinger();
diff --git a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java
index f42ba49..7846173 100644
--- a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java
+++ b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java
@@ -50,6 +50,7 @@
 import android.net.wifi.p2p.WifiP2pInfo;
 import android.os.Bundle;
 import android.os.ParcelUuid;
+import android.telecom.AudioState;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.CellLocation;
@@ -108,6 +109,9 @@
         if (data instanceof Address) {
             return buildJsonAddress((Address) data);
         }
+        if (data instanceof AudioState) {
+            return buildJsonAudioState((AudioState) data);
+        }
         if (data instanceof Location) {
             return buildJsonLocation((Location) data);
         }
@@ -207,6 +211,13 @@
         // throw new JSONException("Failed to build JSON result. " + data.getClass().getName());
     }
 
+    private static JSONObject buildJsonAudioState(AudioState data) throws JSONException {
+        JSONObject state = new JSONObject();
+        state.put("isMuted", data.isMuted);
+        state.put("AudioRoute", AudioState.audioRouteToString(data.route));
+        return state;
+    }
+
     private static Object buildDisplayMetrics(DisplayMetrics data) throws JSONException {
         JSONObject dm = new JSONObject();
         dm.put("widthPixels", data.widthPixels);
diff --git a/ScriptingLayerForAndroid/AndroidManifest.xml b/ScriptingLayerForAndroid/AndroidManifest.xml
index 6f3ddb1..d971b32 100644
--- a/ScriptingLayerForAndroid/AndroidManifest.xml
+++ b/ScriptingLayerForAndroid/AndroidManifest.xml
@@ -11,8 +11,11 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.BIND_INCALL_SERVICE" />
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.CALL_PRIVILEGED" />
+    <uses-permission android:name="com.android.server.telecom.permission.REGISTER_PROVIDER_OR_SUBSCRIPTION" />
+    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
     <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
     <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@@ -156,6 +159,12 @@
         <activity android:name="org.connectbot.HelpTopicActivity" android:configChanges="keyboardHidden|orientation" />
         <service android:name=".activity.ScriptingLayerService" />
         <service android:name=".activity.TriggerService" />
+        <service android:name="com.googlecode.android_scripting.facade.tele.InCallServiceImpl"
+                 android:permission="android.permission.BIND_INCALL_SERVICE" >
+            <intent-filter>
+                <action android:name="android.telecom.InCallService"/>
+            </intent-filter>
+        </service>
         <activity android:name=".activity.InterpreterManager" android:launchMode="singleTask" android:configChanges="keyboardHidden|orientation" />
         <activity android:name=".activity.LogcatViewer" android:launchMode="singleTask" android:configChanges="keyboardHidden|orientation" />
         <activity android:name=".activity.ScriptsLiveFolder" android:label="Scripts" android:icon="@drawable/live_folder" android:configChanges="keyboardHidden|orientation">