From 53e1ce35f713139629cb24df6ea1245e11464a42 Mon Sep 17 00:00:00 2001 From: sail Date: Thu, 31 Aug 2017 16:48:09 -0700 Subject: 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 --- java/com/android/dialer/simulator/Simulator.java | 48 +++++++++++++++++ .../dialer/simulator/impl/SimulatorConnection.java | 60 +++++++++++++++++++--- .../simulator/impl/SimulatorConnectionService.java | 50 +++++++++++++++--- .../simulator/impl/SimulatorSpamCallCreator.java | 2 +- .../dialer/simulator/impl/SimulatorVoiceCall.java | 2 +- .../impl/answermethod/AnswerMethodFactory.java | 13 +++++ java/com/android/incallui/call/CallList.java | 5 ++ 7 files changed, 164 insertions(+), 16 deletions(-) (limited to 'java') diff --git a/java/com/android/dialer/simulator/Simulator.java b/java/com/android/dialer/simulator/Simulator.java index 78058a48f..f4164158e 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 12d095890..b462b5405 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 listeners = new ArrayList<>(); + private final List 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 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 9e107edee..06c2591cc 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java +++ b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java @@ -40,6 +40,7 @@ public class SimulatorConnectionService extends ConnectionService { 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 listeners = new ArrayList<>(); + private static SimulatorConnectionService instance; private static void register(@NonNull Context context) { LogUtil.enterBlock("SimulatorConnectionService.register"); @@ -55,6 +56,30 @@ public class SimulatorConnectionService extends ConnectionService { .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 class SimulatorConnectionService extends ConnectionService { } 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 @@ -111,6 +134,19 @@ public class SimulatorConnectionService extends ConnectionService { return Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null); } + @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) { @@ -127,10 +163,12 @@ public class SimulatorConnectionService extends ConnectionService { } 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 4b1d7a564..ae97bc162 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java +++ b/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java @@ -88,7 +88,7 @@ final class SimulatorSpamCallCreator implements SimulatorConnectionService.Liste 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 5930dff24..2512828b8 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java +++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java @@ -23,7 +23,7 @@ import com.android.dialer.common.LogUtil; /** 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 35f36f727..ccb132b95 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 @@ package com.android.incallui.answer.impl.answermethod; 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 @@ public class AnswerMethodFactory { 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 d0931dd3d..954fdc910 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.call.DialerCall.State; 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 @@ public class CallList implements DialerCallDelegate { return mCallById.get(callId); } + public Collection 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); -- cgit v1.2.3