Use simulator to add in-call UI integration tests

This CL uses the simulator connection service to perform integration
tests for incallui.

The main pieces of this CL are:
  - DialerCallEvent - this is how we track changes to the incallui calls
  - Simulator.Event - this is how we track changes to a simulator
  connection

With the above two we can do things like:
  - block until a DialerCall switches from ACTIVE TO ONHOLD:
      - DialerCallEspresso.waitForNextEvent(tracker, call, new
      DialerCallEvent(STATE_CHANGE, "ACTIVE", "ONHOLD")
  - block for a connection to recive a particular DTMF code:
      - SimulatorConnectionEspresso.waitForNextEvent(call, Event.DTMF)

Future CLs will include things like:
  - fling to answer / reject
  - conference calls
  - screenshot diffing
  - video calling

Test: InCallActivityTest
PiperOrigin-RevId: 167211015
Change-Id: Ib013b10fe963092fad0816b07b1659efd69d9468
diff --git a/java/com/android/dialer/simulator/Simulator.java b/java/com/android/dialer/simulator/Simulator.java
index 78058a4..f416415 100644
--- a/java/com/android/dialer/simulator/Simulator.java
+++ b/java/com/android/dialer/simulator/Simulator.java
@@ -17,11 +17,59 @@
 package com.android.dialer.simulator;
 
 import android.content.Context;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
 import android.view.ActionProvider;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /** Used to add menu items to the Dialer menu to test the app using simulated calls and data. */
 public interface Simulator {
   boolean shouldShow();
 
   ActionProvider getActionProvider(Context context);
+
+  /** Information about a connection event. */
+  public static class Event {
+    /** The type of connection event. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+      NONE,
+      ANSWER,
+      REJECT,
+      HOLD,
+      UNHOLD,
+      DISCONNECT,
+      STATE_CHANGE,
+      DTMF,
+    })
+    public @interface Type {}
+
+    public static final int NONE = -1;
+    public static final int ANSWER = 1;
+    public static final int REJECT = 2;
+    public static final int HOLD = 3;
+    public static final int UNHOLD = 4;
+    public static final int DISCONNECT = 5;
+    public static final int STATE_CHANGE = 6;
+    public static final int DTMF = 7;
+
+    @Type public final int type;
+    /** Holds event specific information. For example, for DTMF this could be the keycode. */
+    @Nullable public final String data1;
+    /**
+     * Holds event specific information. For example, for STATE_CHANGE this could be the new state.
+     */
+    @Nullable public final String data2;
+
+    public Event(@Type int type) {
+      this(type, null, null);
+    }
+
+    public Event(@Type int type, String data1, String data2) {
+      this.type = type;
+      this.data1 = data1;
+      this.data2 = data2;
+    }
+  }
 }
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnection.java b/java/com/android/dialer/simulator/impl/SimulatorConnection.java
index 12d0958..b462b54 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorConnection.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorConnection.java
@@ -16,41 +16,85 @@
 
 package com.android.dialer.simulator.impl;
 
+import android.support.annotation.NonNull;
 import android.telecom.Connection;
-import android.telecom.DisconnectCause;
+import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
+import com.android.dialer.simulator.Simulator.Event;
+import java.util.ArrayList;
+import java.util.List;
 
 /** Represents a single phone call on the device. */
-final class SimulatorConnection extends Connection {
+public final class SimulatorConnection extends Connection {
+  private final List<Listener> listeners = new ArrayList<>();
+  private final List<Event> events = new ArrayList<>();
+  private int currentState = STATE_NEW;
+
+  public void addListener(@NonNull Listener listener) {
+    listeners.add(Assert.isNotNull(listener));
+  }
+
+  public void removeListener(@NonNull Listener listener) {
+    listeners.remove(Assert.isNotNull(listener));
+  }
+
+  @NonNull
+  public List<Event> getEvents() {
+    return events;
+  }
 
   @Override
   public void onAnswer() {
     LogUtil.enterBlock("SimulatorConnection.onAnswer");
-    setActive();
+    onEvent(new Event(Event.ANSWER));
   }
 
   @Override
   public void onReject() {
     LogUtil.enterBlock("SimulatorConnection.onReject");
-    setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
+    onEvent(new Event(Event.REJECT));
   }
 
   @Override
   public void onHold() {
     LogUtil.enterBlock("SimulatorConnection.onHold");
-    setOnHold();
+    onEvent(new Event(Event.HOLD));
   }
 
   @Override
   public void onUnhold() {
     LogUtil.enterBlock("SimulatorConnection.onUnhold");
-    setActive();
+    onEvent(new Event(Event.UNHOLD));
   }
 
   @Override
   public void onDisconnect() {
     LogUtil.enterBlock("SimulatorConnection.onDisconnect");
-    setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
-    destroy();
+    onEvent(new Event(Event.DISCONNECT));
+  }
+
+  @Override
+  public void onStateChanged(int newState) {
+    LogUtil.enterBlock("SimulatorConnection.onStateChanged");
+    onEvent(new Event(Event.STATE_CHANGE, stateToString(currentState), stateToString(newState)));
+    currentState = newState;
+  }
+
+  @Override
+  public void onPlayDtmfTone(char c) {
+    LogUtil.enterBlock("SimulatorConnection.onPlayDtmfTone");
+    onEvent(new Event(Event.DTMF, Character.toString(c), null));
+  }
+
+  private void onEvent(@NonNull Event event) {
+    events.add(Assert.isNotNull(event));
+    for (Listener listener : listeners) {
+      listener.onEvent(this, event);
+    }
+  }
+
+  /** Callback for when a new event arrives. */
+  public interface Listener {
+    void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event);
   }
 }
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java
index 9e107ed..06c2591 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java
@@ -40,6 +40,7 @@
   private static final String PHONE_ACCOUNT_ID = "SIMULATOR_ACCOUNT_ID";
   private static final String EXTRA_IS_SIMULATOR_CONNECTION = "is_simulator_connection";
   private static final List<Listener> listeners = new ArrayList<>();
+  private static SimulatorConnectionService instance;
 
   private static void register(@NonNull Context context) {
     LogUtil.enterBlock("SimulatorConnectionService.register");
@@ -55,6 +56,30 @@
         .unregisterPhoneAccount(buildPhoneAccount(context).getAccountHandle());
   }
 
+  public static SimulatorConnectionService getInstance() {
+    return instance;
+  }
+
+  public static void addNewOutgoingCall(
+      @NonNull Context context, @NonNull Bundle extras, @NonNull String phoneNumber) {
+    LogUtil.enterBlock("SimulatorConnectionService.addNewOutgoingCall");
+    Assert.isNotNull(context);
+    Assert.isNotNull(extras);
+    Assert.isNotNull(phoneNumber);
+
+    register(context);
+
+    Bundle bundle = new Bundle(extras);
+    bundle.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true);
+    Bundle outgoingCallExtras = new Bundle();
+    outgoingCallExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, bundle);
+
+    // Use the system's phone account so that these look like regular SIM call.
+    TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+    telecomManager.placeCall(
+        Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null), outgoingCallExtras);
+  }
+
   public static void addNewIncomingCall(
       @NonNull Context context, @NonNull Bundle extras, @NonNull String callerId) {
     LogUtil.enterBlock("SimulatorConnectionService.addNewIncomingCall");
@@ -76,13 +101,11 @@
   }
 
   public static void addListener(@NonNull Listener listener) {
-    Assert.isNotNull(listener);
-    listeners.add(listener);
+    listeners.add(Assert.isNotNull(listener));
   }
 
   public static void removeListener(@NonNull Listener listener) {
-    Assert.isNotNull(listener);
-    listeners.remove(listener);
+    listeners.remove(Assert.isNotNull(listener));
   }
 
   @NonNull
@@ -112,6 +135,19 @@
   }
 
   @Override
+  public void onCreate() {
+    super.onCreate();
+    instance = this;
+  }
+
+  @Override
+  public void onDestroy() {
+    LogUtil.enterBlock("SimulatorConnectionService.onDestroy");
+    instance = null;
+    super.onDestroy();
+  }
+
+  @Override
   public Connection onCreateOutgoingConnection(
       PhoneAccountHandle phoneAccount, ConnectionRequest request) {
     LogUtil.enterBlock("SimulatorConnectionService.onCreateOutgoingConnection");
@@ -127,10 +163,12 @@
     }
 
     SimulatorConnection connection = new SimulatorConnection();
-    connection.setActive();
+    connection.setDialing();
     connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
     connection.setConnectionCapabilities(
-        Connection.CAPABILITY_MUTE | Connection.CAPABILITY_SUPPORT_HOLD);
+        Connection.CAPABILITY_MUTE
+            | Connection.CAPABILITY_SUPPORT_HOLD
+            | Connection.CAPABILITY_HOLD);
     connection.putExtras(request.getExtras());
 
     for (Listener listener : listeners) {
diff --git a/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java b/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java
index 4b1d7a5..ae97bc1 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java
@@ -88,7 +88,7 @@
     extras.putInt(EXTRA_CALL_COUNT, callCount - 1);
     extras.putBoolean(EXTRA_IS_SPAM_CALL_CONNECTION, true);
 
-    // We need to clear the call log because spam notifiations are only shown for new calls.
+    // We need to clear the call log because spam notifications are only shown for new calls.
     clearCallLog(context);
 
     SimulatorConnectionService.addNewIncomingCall(context, extras, callerId);
diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
index 5930dff..2512828 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
@@ -23,7 +23,7 @@
 
 /** Utilities to simulate phone calls. */
 final class SimulatorVoiceCall {
-  public static void addNewIncomingCall(@NonNull Context context) {
+  static void addNewIncomingCall(@NonNull Context context) {
     LogUtil.enterBlock("SimulatorVoiceCall.addNewIncomingCall");
     // Set the caller ID to the Google London office.
     String callerId = "+44 (0) 20 7031 3000";
diff --git a/java/com/android/incallui/answer/impl/answermethod/AnswerMethodFactory.java b/java/com/android/incallui/answer/impl/answermethod/AnswerMethodFactory.java
index 35f36f7..ccb132b 100644
--- a/java/com/android/incallui/answer/impl/answermethod/AnswerMethodFactory.java
+++ b/java/com/android/incallui/answer/impl/answermethod/AnswerMethodFactory.java
@@ -19,12 +19,15 @@
 import android.app.Activity;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.app.Fragment;
+import com.android.dialer.common.LogUtil;
 import com.android.dialer.compat.ActivityCompat;
 import com.android.incallui.util.AccessibilityUtil;
 
 /** Creates the appropriate {@link AnswerMethod} for the circumstances. */
 public class AnswerMethodFactory {
+  private static boolean shouldUseTwoButtonMethodForTesting;
 
   @NonNull
   public static AnswerMethod createAnswerMethod(@NonNull Activity activity) {
@@ -45,7 +48,17 @@
     return !(answerMethod instanceof TwoButtonMethod) && needTwoButton(answerMethod.getActivity());
   }
 
+  @VisibleForTesting
+  public static void setShouldUseTwoButtonMethodForTesting(boolean shouldUse) {
+    shouldUseTwoButtonMethodForTesting = shouldUse;
+  }
+
   private static boolean needTwoButton(@NonNull Activity activity) {
+    if (shouldUseTwoButtonMethodForTesting) {
+      LogUtil.i("AnswerMethodFactory.needTwoButton", "enabled for testing");
+      return true;
+    }
+
     return AccessibilityUtil.isTouchExplorationEnabled(activity)
         || ActivityCompat.isInMultiWindowMode(activity);
   }
diff --git a/java/com/android/incallui/call/CallList.java b/java/com/android/incallui/call/CallList.java
index d0931dd..954fdc9 100644
--- a/java/com/android/incallui/call/CallList.java
+++ b/java/com/android/incallui/call/CallList.java
@@ -44,6 +44,7 @@
 import com.android.incallui.latencyreport.LatencyReport;
 import com.android.incallui.util.TelecomCallUtil;
 import com.android.incallui.videotech.utils.SessionModificationState;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.Map;
@@ -509,6 +510,10 @@
     return mCallById.get(callId);
   }
 
+  public Collection<DialerCall> getAllCalls() {
+    return mCallById.values();
+  }
+
   /** Returns first call found in the call map with the specified state. */
   public DialerCall getFirstCallWithState(int state) {
     return getCallWithState(state, 0);