Test Call Service: Add handoff support

This CL adds support for switching from TestCallService to
PSTN and back. Pressing one one the keypad enables handoff
and pressing 2 disables handoff.

Change-Id: Ide576c4ed3c45efd8605da5dafc8253f2658893c
diff --git a/tests/src/com/android/telecomm/testcallservice/CallServiceNotifier.java b/tests/src/com/android/telecomm/testcallservice/CallServiceNotifier.java
index a186e58..e88c293 100644
--- a/tests/src/com/android/telecomm/testcallservice/CallServiceNotifier.java
+++ b/tests/src/com/android/telecomm/testcallservice/CallServiceNotifier.java
@@ -26,12 +26,10 @@
 import android.util.Log;
 
 /**
- * Class used to create, update and cancel the notification used to display and update call state for
- * {@link TestCallService}.
+ * Class used to create, update and cancel the notification used to display and update call state
+ * for {@link TestCallService}.
  */
 public class CallServiceNotifier {
-    private static final String TAG = CallServiceNotifier.class.getSimpleName();
-
     private static final CallServiceNotifier INSTANCE = new CallServiceNotifier();
 
     /**
@@ -56,7 +54,7 @@
      * Updates the notification in the notification pane.
      */
     public void updateNotification(Context context) {
-        Log.i("CallServiceNotifier", "adding the notification ------------");
+        log("adding the notification ------------");
         getNotificationManager(context).notify(CALL_NOTIFICATION_ID, getNotification(context));
     }
 
@@ -64,7 +62,7 @@
      * Cancels the notification.
      */
     public void cancelNotification(Context context) {
-        Log.i(TAG, "canceling notification");
+        log("canceling notification");
         getNotificationManager(context).cancel(CALL_NOTIFICATION_ID);
     }
 
@@ -110,7 +108,7 @@
      * Creates the intent to add an incoming call through Telecomm.
      */
     private PendingIntent createIncomingCallIntent(Context context) {
-        Log.i(TAG, "Creating incoming call pending intent.");
+        log("Creating incoming call pending intent.");
         // Build descriptor for TestCallService.
         CallServiceDescriptor.Builder descriptorBuilder = CallServiceDescriptor.newBuilder(context);
         descriptorBuilder.setCallService(TestCallService.class);
@@ -141,4 +139,8 @@
     private void addExitAction(Notification.Builder builder, Context context) {
         builder.addAction(0, "Exit", createExitIntent(context));
     }
+
+    private static void log(String msg) {
+        Log.w("testcallservice", "[CallServiceNotifier] " + msg);
+    }
 }
diff --git a/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java b/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
index c267867..86319a4 100644
--- a/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
+++ b/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
@@ -16,33 +16,100 @@
 
 package com.android.telecomm.testcallservice;
 
+import android.net.Uri;
+import android.os.Bundle;
 import android.telecomm.CallInfo;
 import android.telecomm.CallServiceDescriptor;
 import android.telecomm.CallServiceSelector;
 import android.telecomm.CallServiceSelectorAdapter;
+import android.util.Log;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 
 import java.util.List;
 
-/**
- * Dummy call-service selector which returns the list of call services in the same order in which it
- * was given. Also returns false for every request on switchability.
- */
+/** Simple selector to exercise Telecomm code. */
 public class DummyCallServiceSelector extends CallServiceSelector {
+    private static DummyCallServiceSelector sInstance;
+    private static final String SCHEME_TEL = "tel";
+    private static final String TELEPHONY_PACKAGE_NAME =
+            "com.android.phone";
+    private static final String CUSTOM_HANDOFF_KEY = "custom_handoff_key";
+    private static final String CUSTOM_HANDOFF_VALUE = "custom_handoff_value";
+
+    public DummyCallServiceSelector() {
+        log("constructor");
+        Preconditions.checkState(sInstance == null);
+        sInstance = this;
+    }
+
+    static DummyCallServiceSelector getInstance() {
+        Preconditions.checkNotNull(sInstance);
+        return sInstance;
+    }
 
     @Override
     protected void select(CallInfo callInfo, List<CallServiceDescriptor> descriptors) {
+        log("select");
         List<CallServiceDescriptor> orderedList = Lists.newLinkedList();
 
-        // Make sure that the test call services are the only ones
+        boolean shouldHandoffToPstn = false;
+        if (callInfo.getCurrentCallServiceDescriptor() != null) {
+            // If the current call service is TestCallService then handoff to PSTN, otherwise
+            // handoff to TestCallService.
+            shouldHandoffToPstn = isTestCallService(callInfo.getCurrentCallServiceDescriptor());
+            String extraValue = callInfo.getExtras().getString(CUSTOM_HANDOFF_KEY);
+            log("handing off, toPstn: " + shouldHandoffToPstn + ", extraValue: " + extraValue);
+            Preconditions.checkState(CUSTOM_HANDOFF_VALUE.equals(extraValue));
+        }
+
         for (CallServiceDescriptor descriptor : descriptors) {
-            String packageName = descriptor.getServiceComponent().getPackageName();
-            if (getPackageName().equals(packageName)) {
+            if (isTestCallService(descriptor) && !shouldHandoffToPstn) {
+                orderedList.add(0, descriptor);
+            } else if (isPstnCallService(descriptor)) {
                 orderedList.add(descriptor);
+            } else {
+                log("skipping call service: " + descriptor.getServiceComponent());
             }
         }
 
         getAdapter().setSelectedCallServices(callInfo.getId(), orderedList);
     }
+
+    void sendHandoffInfo(Uri remoteHandle, Uri handoffHandle) {
+        log("sendHandoffInfo");
+        String callId = findMatchingCall(remoteHandle);
+        Preconditions.checkNotNull(callId);
+        Bundle extras = new Bundle();
+        extras.putString(CUSTOM_HANDOFF_KEY, CUSTOM_HANDOFF_VALUE);
+        getAdapter().setHandoffInfo(callId, handoffHandle, extras);
+    }
+
+    private String findMatchingCall(Uri remoteHandle) {
+        for (CallInfo callInfo : getCalls()) {
+            if (remoteHandle.equals(callInfo.getOriginalHandle())) {
+                return callInfo.getId();
+            }
+        }
+        return null;
+    }
+
+    private boolean isTestCallService(CallServiceDescriptor descriptor) {
+        if (descriptor == null) {
+            return false;
+        }
+        return getPackageName().equals(descriptor.getServiceComponent().getPackageName());
+    }
+
+    private boolean isPstnCallService(CallServiceDescriptor descriptor) {
+        if (descriptor == null) {
+            return false;
+        }
+        return TELEPHONY_PACKAGE_NAME.equals(descriptor.getServiceComponent().getPackageName());
+    }
+
+    private static void log(String msg) {
+        Log.w("testcallservice", "[DummyCallServiceSelector] " + msg);
+    }
 }
diff --git a/tests/src/com/android/telecomm/testcallservice/TestCallService.java b/tests/src/com/android/telecomm/testcallservice/TestCallService.java
index f6e2da7..275c208 100644
--- a/tests/src/com/android/telecomm/testcallservice/TestCallService.java
+++ b/tests/src/com/android/telecomm/testcallservice/TestCallService.java
@@ -20,6 +20,7 @@
 import android.media.MediaPlayer;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.telecomm.CallAudioState;
 import android.telecomm.CallInfo;
 import android.telecomm.CallService;
@@ -31,35 +32,27 @@
 import com.android.telecomm.tests.R;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
-import com.google.common.collect.Sets;
+import com.google.common.collect.Maps;
 
-import java.util.Set;
+import java.util.Map;
 
 /**
  * Service which provides fake calls to test the ICallService interface.
  * TODO(santoscordon): Rename all classes in the directory to Dummy* (e.g., DummyCallService).
  */
 public class TestCallService extends CallService {
-    private static final String TAG = TestCallService.class.getSimpleName();
+    private static final String SCHEME_TEL = "tel";
 
-    /**
-     * Set of call IDs for live (active, ringing, dialing) calls.
-     * TODO(santoscordon): Reference CallState javadoc when available for the different call states.
-     */
-    private Set<String> mLiveCallIds;
+    private final Map<String, CallInfo> mCalls = Maps.newHashMap();
+    private final Handler mHandler = new Handler();
 
-    /**
-     * Used to play an audio tone during a call.
-     */
+    /** Used to play an audio tone during a call. */
     private MediaPlayer mMediaPlayer;
 
     /** {@inheritDoc} */
     @Override
     public void onAdapterAttached(CallServiceAdapter callServiceAdapter) {
-        Log.i(TAG, "setCallServiceAdapter()");
-
-        mLiveCallIds = Sets.newHashSet();
-
+        log("onAdapterAttached");
         mMediaPlayer = createMediaPlayer();
     }
 
@@ -71,75 +64,79 @@
      */
     @Override
     public void isCompatibleWith(CallInfo callInfo) {
-        Log.i(TAG, "isCompatibleWith(" + callInfo + ")");
+        log("isCompatibleWith, callInfo: " + callInfo);
         Preconditions.checkNotNull(callInfo.getHandle());
 
         // Is compatible if the handle doesn't start with 7.
         boolean isCompatible = !callInfo.getHandle().getSchemeSpecificPart().startsWith("7");
 
-        // Tell CallsManager whether this call service can place the call (is compatible).
-        // Returning positively on setCompatibleWith() doesn't guarantee that we will be chosen
-        // to place the call. If we *are* chosen then CallsManager will execute the call()
-        // method below.
+        // Tell CallsManager whether this call service can place the call.
         getAdapter().setIsCompatibleWith(callInfo.getId(), isCompatible);
     }
 
     /**
-     * Starts a call by calling into the adapter. For testing purposes this methods acts as if a
-     * call was successfully connected every time.
+     * Starts a call by calling into the adapter.
      *
      * {@inheritDoc}
      */
     @Override
-    public void call(CallInfo callInfo) {
+    public void call(final CallInfo callInfo) {
         String number = callInfo.getHandle().getSchemeSpecificPart();
-        Log.i(TAG, "call(" + number + ")");
+        log("call, number: " + number);
 
         // Crash on 555-DEAD to test call service crashing.
         if ("5550340".equals(number)) {
             throw new RuntimeException("Goodbye, cruel world.");
         }
 
-        createCall(callInfo.getId());
+        mCalls.put(callInfo.getId(), callInfo);
         getAdapter().handleSuccessfulOutgoingCall(callInfo.getId());
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                activateCall(callInfo.getId());
+            }
+        }, 4000);
     }
 
     /** {@inheritDoc} */
     @Override
     public void abort(String callId) {
-        Log.i(TAG, "abort(" + callId + ")");
+        log("abort, callId: " + callId);
         destroyCall(callId);
     }
 
     /** {@inheritDoc} */
     @Override
     public void setIncomingCallId(String callId, Bundle extras) {
-        Log.i(TAG, "setIncomingCallId(" + callId + ", " + extras + ")");
+        log("setIncomingCallId, callId: " + callId + " extras: " + extras);
 
         // Use dummy number for testing incoming calls.
-        Uri handle = Uri.fromParts("tel", "5551234", null);
+        Uri handle = Uri.fromParts(SCHEME_TEL, "5551234", null);
 
         CallInfo callInfo = new CallInfo(callId, CallState.RINGING, handle);
+        mCalls.put(callInfo.getId(), callInfo);
         getAdapter().notifyIncomingCall(callInfo);
     }
 
     /** {@inheritDoc} */
     @Override
     public void answer(String callId) {
-        getAdapter().setActive(callId);
-        createCall(callId);
+        log("answer, callId: " + callId);
+        activateCall(callId);
     }
 
     /** {@inheritDoc} */
     @Override
     public void reject(String callId) {
+        log("reject, callId: " + callId);
         getAdapter().setDisconnected(callId, DisconnectCause.INCOMING_REJECTED, null);
     }
 
     /** {@inheritDoc} */
     @Override
     public void disconnect(String callId) {
-        Log.i(TAG, "disconnect(" + callId + ")");
+        log("disconnect, callId: " + callId);
 
         destroyCall(callId);
         getAdapter().setDisconnected(callId, DisconnectCause.LOCAL, null);
@@ -148,55 +145,45 @@
     /** {@inheritDoc} */
     @Override
     public void hold(String callId) {
-        Log.i(TAG, "hold(" + callId + ")");
+        log("hold, callId: " + callId);
         getAdapter().setOnHold(callId);
     }
 
     /** {@inheritDoc} */
     @Override
     public void unhold(String callId) {
-        Log.i(TAG, "unhold(" + callId + ")");
+        log("unhold, callId: " + callId);
         getAdapter().setActive(callId);
     }
 
     /** {@inheritDoc} */
     @Override
     public void playDtmfTone(String callId, char digit) {
-        Log.i(TAG, "playDtmfTone(" + callId + "," + digit + ")");
-        // TODO(ihab): Implement
+        log("playDtmfTone, callId: " + callId + " digit: " + digit);
     }
 
     /** {@inheritDoc} */
     @Override
     public void stopDtmfTone(String callId) {
-        Log.i(TAG, "stopDtmfTone(" + callId + ")");
-        // TODO(ihab): Implement
+        log("stopDtmfTone, callId: " + callId);
     }
 
     /** {@inheritDoc} */
     @Override
     public void onAudioStateChanged(String callId, CallAudioState audioState) {
+        log("onAudioStateChanged, callId: " + callId + " audioState: " + audioState);
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean onUnbind(Intent intent) {
+        log("onUnbind");
         mMediaPlayer = null;
-
         return super.onUnbind(intent);
     }
 
-    /**
-     * Adds the specified call ID to the set of live call IDs and starts playing audio on the
-     * voice-call stream.
-     *
-     * @param callId The identifier of the call to create.
-     */
-    private void createCall(String callId) {
-        Preconditions.checkState(!Strings.isNullOrEmpty(callId));
-        mLiveCallIds.add(callId);
-
-        // Starts audio if not already started.
+    private void activateCall(String callId) {
+        getAdapter().setActive(callId);
         if (!mMediaPlayer.isPlaying()) {
             mMediaPlayer.start();
         }
@@ -210,10 +197,10 @@
      */
     private void destroyCall(String callId) {
         Preconditions.checkState(!Strings.isNullOrEmpty(callId));
-        mLiveCallIds.remove(callId);
+        mCalls.remove(callId);
 
         // Stops audio if there are no more calls.
-        if (mLiveCallIds.isEmpty() && mMediaPlayer.isPlaying()) {
+        if (mCalls.isEmpty() && mMediaPlayer.isPlaying()) {
             mMediaPlayer.stop();
             mMediaPlayer.release();
             mMediaPlayer = createMediaPlayer();
@@ -227,4 +214,7 @@
         return mediaPlayer;
     }
 
+    private static void log(String msg) {
+        Log.w("testcallservice", "[TestCallService] " + msg);
+    }
 }
diff --git a/tests/src/com/android/telecomm/testcallservice/TestCallServiceProvider.java b/tests/src/com/android/telecomm/testcallservice/TestCallServiceProvider.java
index 781f629..3a6646c 100644
--- a/tests/src/com/android/telecomm/testcallservice/TestCallServiceProvider.java
+++ b/tests/src/com/android/telecomm/testcallservice/TestCallServiceProvider.java
@@ -28,12 +28,10 @@
  * TODO(santoscordon): Build more dummy providers for more CallServiceDescriptor.FLAG_* types.
  */
 public class TestCallServiceProvider extends CallServiceProvider {
-    private static final String TAG = TestCallServiceProvider.class.getSimpleName();
-
     /** {@inheritDoc} */
     @Override
     public void lookupCallServices(CallServiceLookupResponse response) {
-        Log.i(TAG, "lookupCallServices()");
+        log("lookupCallServices");
 
         CallServiceDescriptor.Builder builder = CallServiceDescriptor.newBuilder(this);
         builder.setCallService(TestCallService.class);
@@ -41,4 +39,8 @@
 
         response.setCallServiceDescriptors(Lists.newArrayList(builder.build()));
     }
+
+    private static void log(String msg) {
+        Log.w("testcallservice", "[TestCallServiceProvider] " + msg);
+    }
 }