From 2f8d48f5e981ab9945c907cd9ab0f6bd6a82db22 Mon Sep 17 00:00:00 2001 From: yueg Date: Mon, 16 Oct 2017 14:23:26 -0700 Subject: Add GSM conference calling to simulator. This CL adds a new item to the simulator menu: - Add GSM conference The GSM conference action creates a conference with 5 phone calls. Users can individually separate or kick calls out of the conference. Hanging up the second last call finishes the conference. Bug: 67785540 Test: SimulatorConferenceTest PiperOrigin-RevId: 172377631 Change-Id: Ic30fa6c65cf782247f75bcdd1ecbd86b1c16f143 --- java/com/android/dialer/simulator/Simulator.java | 20 ++ .../dialer/simulator/impl/SimulatorConference.java | 168 +++++++++++++++ .../simulator/impl/SimulatorConferenceCreator.java | 229 +++++++++++++++++++++ .../dialer/simulator/impl/SimulatorConnection.java | 6 +- .../simulator/impl/SimulatorConnectionService.java | 20 +- .../simulator/impl/SimulatorMissedCallCreator.java | 4 + .../simulator/impl/SimulatorSimCallManager.java | 43 +++- .../simulator/impl/SimulatorSpamCallCreator.java | 4 + .../dialer/simulator/impl/SimulatorVideoCall.java | 11 +- .../dialer/simulator/impl/SimulatorVoiceCall.java | 30 ++- 10 files changed, 508 insertions(+), 27 deletions(-) create mode 100644 java/com/android/dialer/simulator/impl/SimulatorConference.java create mode 100644 java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java (limited to 'java/com/android/dialer/simulator') diff --git a/java/com/android/dialer/simulator/Simulator.java b/java/com/android/dialer/simulator/Simulator.java index f753e5f6b..4812fa5d6 100644 --- a/java/com/android/dialer/simulator/Simulator.java +++ b/java/com/android/dialer/simulator/Simulator.java @@ -30,6 +30,16 @@ public interface Simulator { ActionProvider getActionProvider(Context context); + /** The type of conference to emulate. */ + // TODO(b/67785540): add VoLTE and CDMA conference call + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + CONFERENCE_TYPE_GSM, + }) + @interface ConferenceType {} + + static final int CONFERENCE_TYPE_GSM = 1; + /** Information about a connection event. */ public static class Event { /** The type of connection event. */ @@ -44,6 +54,11 @@ public interface Simulator { STATE_CHANGE, DTMF, SESSION_MODIFY_REQUEST, + CALL_AUDIO_STATE_CHANGED, + CONNECTION_ADDED, + MERGE, + SEPARATE, + SWAP, }) public @interface Type {} @@ -56,6 +71,11 @@ public interface Simulator { public static final int STATE_CHANGE = 6; public static final int DTMF = 7; public static final int SESSION_MODIFY_REQUEST = 8; + public static final int CALL_AUDIO_STATE_CHANGED = 9; + public static final int CONNECTION_ADDED = 10; + public static final int MERGE = 11; + public static final int SEPARATE = 12; + public static final int SWAP = 13; @Type public final int type; /** Holds event specific information. For example, for DTMF this could be the keycode. */ diff --git a/java/com/android/dialer/simulator/impl/SimulatorConference.java b/java/com/android/dialer/simulator/impl/SimulatorConference.java new file mode 100644 index 000000000..7468b56b5 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorConference.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.simulator.impl; + +import android.support.annotation.NonNull; +import android.telecom.CallAudioState; +import android.telecom.Conference; +import android.telecom.Connection; +import android.telecom.PhoneAccountHandle; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.simulator.Simulator; +import com.android.dialer.simulator.Simulator.Event; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a conference call. When a user merges two phone calls we create an instance of this + * conference object and add it to the connection service. All operations such as hold and DTMF are + * then performed on this object. + */ +public final class SimulatorConference extends Conference implements SimulatorConnection.Listener { + static final int PROPERTY_GENERIC_CONFERENCE = 1 << 1; + + private final List listeners = new ArrayList<>(); + private final List events = new ArrayList<>(); + private final int conferenceType; + + private SimulatorConference( + PhoneAccountHandle handle, @Simulator.ConferenceType int conferenceType) { + super(handle); + this.conferenceType = conferenceType; + setActive(); + } + + static SimulatorConference newGsmConference(PhoneAccountHandle handle) { + SimulatorConference simulatorConference = + new SimulatorConference(handle, Simulator.CONFERENCE_TYPE_GSM); + simulatorConference.setConnectionCapabilities( + Connection.CAPABILITY_MUTE + | Connection.CAPABILITY_SUPPORT_HOLD + | Connection.CAPABILITY_HOLD + | Connection.CAPABILITY_MANAGE_CONFERENCE); + return simulatorConference; + } + + 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 onCallAudioStateChanged(CallAudioState state) { + LogUtil.enterBlock("SimulatorConference.onCallAudioStateChanged"); + onEvent(new Event(Event.CALL_AUDIO_STATE_CHANGED)); + } + + @Override + public void onConnectionAdded(Connection connection) { + LogUtil.enterBlock("SimulatorConference.onConnectionAdded"); + onEvent( + new Event( + Event.CONNECTION_ADDED, SimulatorSimCallManager.getConnectionTag(connection), null)); + ((SimulatorConnection) connection).addListener(this); + } + + @Override + public void onDisconnect() { + LogUtil.enterBlock("SimulatorConference.onDisconnect"); + onEvent(new Event(Event.DISCONNECT)); + } + + @Override + public void onHold() { + LogUtil.enterBlock("SimulatorConference.onHold"); + onEvent(new Event(Event.HOLD)); + } + + @Override + public void onMerge(Connection connection) { + LogUtil.i("SimulatorConference.onMerge", "connection: " + connection); + onEvent(new Event(Event.MERGE, SimulatorSimCallManager.getConnectionTag(connection), null)); + } + + @Override + public void onMerge() { + LogUtil.enterBlock("SimulatorConference.onMerge"); + onEvent(new Event(Event.MERGE)); + } + + @Override + public void onPlayDtmfTone(char c) { + LogUtil.enterBlock("SimulatorConference.onPlayDtmfTone"); + onEvent(new Event(Event.DTMF, Character.toString(c), null)); + } + + @Override + public void onSeparate(Connection connection) { + LogUtil.i("SimulatorConference.onSeparate", "connection: " + connection); + onEvent(new Event(Event.SEPARATE, SimulatorSimCallManager.getConnectionTag(connection), null)); + } + + @Override + public void onSwap() { + LogUtil.enterBlock("SimulatorConference.onSwap"); + onEvent(new Event(Event.SWAP)); + } + + @Override + public void onUnhold() { + LogUtil.enterBlock("SimulatorConference.onUnhold"); + onEvent(new Event(Event.UNHOLD)); + } + + @Override + public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) { + if (conferenceType == Simulator.CONFERENCE_TYPE_GSM) { + onGsmEvent(connection, event); + } + } + + private void onGsmEvent(@NonNull SimulatorConnection connection, @NonNull Event event) { + if (event.type == Event.STATE_CHANGE + && Connection.stateToString(Connection.STATE_DISCONNECTED).equals(event.data2)) { + removeConnection(connection); + connection.removeListener(this); + if (getConnections().size() <= 1) { + // When only one connection exists, it's not conference call anymore + setDisconnected(connection.getDisconnectCause()); + destroy(); + } + } + } + + void onEvent(@NonNull Event event) { + events.add(Assert.isNotNull(event)); + for (Listener listener : new ArrayList<>(listeners)) { + listener.onEvent(this, event); + } + } + + /** Callback for when a new event arrives. */ + public interface Listener { + void onEvent(@NonNull SimulatorConference conference, @NonNull Event event); + } +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java b/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java new file mode 100644 index 000000000..838b58dc2 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.simulator.impl; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.telecom.Conferenceable; +import android.telecom.Connection; +import android.telecom.DisconnectCause; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.ThreadUtil; +import com.android.dialer.simulator.Simulator; +import com.android.dialer.simulator.Simulator.Event; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** Creates a conference with a given number of participants. */ +final class SimulatorConferenceCreator + implements SimulatorConnectionService.Listener, + SimulatorConnection.Listener, + SimulatorConference.Listener { + private static final String EXTRA_CALL_COUNT = "call_count"; + + @NonNull private final Context context; + @NonNull private final List connectionTags = new ArrayList<>(); + @Simulator.ConferenceType private final int conferenceType; + + public SimulatorConferenceCreator( + @NonNull Context context, @Simulator.ConferenceType int conferenceType) { + this.context = Assert.isNotNull(context); + this.conferenceType = conferenceType; + } + + void start(int callCount) { + SimulatorConnectionService.addListener(this); + addNextCall(callCount); + } + + private void addNextCall(int callCount) { + LogUtil.i("SimulatorConferenceCreator.addNextIncomingCall", "callCount: " + callCount); + if (callCount <= 0) { + LogUtil.i("SimulatorConferenceCreator.addNextCall", "done adding calls"); + return; + } + + String callerId = String.format(Locale.US, "+1-650-234%04d", callCount); + Bundle extras = new Bundle(); + extras.putInt(EXTRA_CALL_COUNT, callCount - 1); + connectionTags.add( + SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */, extras)); + } + + @Override + public void onNewIncomingConnection(@NonNull SimulatorConnection connection) { + if (!isMyConnection(connection)) { + LogUtil.i("SimulatorConferenceCreator.onNewOutgoingConnection", "unknown connection"); + return; + } + + LogUtil.i("SimulatorConferenceCreator.onNewOutgoingConnection", "connection created"); + connection.addListener(this); + + // Telecom will force the connection to switch to DIALING when we return it. Wait until after + // we're returned it before changing call state. + ThreadUtil.postOnUiThread(() -> connection.setActive()); + + // Once the connection is active, go ahead and conference it and add the next call. + ThreadUtil.postDelayedOnUiThread( + () -> { + SimulatorConference conference = findCurrentConference(); + if (conference == null) { + Assert.checkArgument(conferenceType == Simulator.CONFERENCE_TYPE_GSM); + conference = + SimulatorConference.newGsmConference( + SimulatorSimCallManager.getSystemPhoneAccountHandle(context)); + conference.addListener(this); + SimulatorConnectionService.getInstance().addConference(conference); + } + updateConferenceableConnections(); + conference.addConnection(connection); + addNextCall(getCallCount(connection)); + }, + 1000); + } + + @Override + public void onNewOutgoingConnection(@NonNull SimulatorConnection connection) {} + + /** + * This is called when the user clicks the merge button. We create the initial conference + * automatically but with this method we can let the user split and merge calls as desired. + */ + @Override + public void onConference( + @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) { + LogUtil.enterBlock("SimulatorConferenceCreator.onConference"); + if (!isMyConnection(connection1) || !isMyConnection(connection2)) { + LogUtil.i("SimulatorConferenceCreator.onConference", "unknown connections, ignoring"); + return; + } + + if (connection1.getConference() != null) { + connection1.getConference().addConnection(connection2); + } else if (connection2.getConference() != null) { + connection2.getConference().addConnection(connection1); + } else { + Assert.checkArgument(conferenceType == Simulator.CONFERENCE_TYPE_GSM); + SimulatorConference conference = + SimulatorConference.newGsmConference( + SimulatorSimCallManager.getSystemPhoneAccountHandle(context)); + conference.addConnection(connection1); + conference.addConnection(connection2); + conference.addListener(this); + SimulatorConnectionService.getInstance().addConference(conference); + } + } + + private boolean isMyConnection(@NonNull Connection connection) { + for (String connectionTag : connectionTags) { + if (connection.getExtras().getBoolean(connectionTag)) { + return true; + } + } + return false; + } + + private void updateConferenceableConnections() { + LogUtil.enterBlock("SimulatorConferenceCreator.updateConferenceableConnections"); + for (String connectionTag : connectionTags) { + SimulatorConnection connection = SimulatorSimCallManager.findConnectionByTag(connectionTag); + List conferenceables = getMyConferenceables(); + conferenceables.remove(connection); + conferenceables.remove(connection.getConference()); + connection.setConferenceables(conferenceables); + } + } + + private List getMyConferenceables() { + List conferenceables = new ArrayList<>(); + for (String connectionTag : connectionTags) { + SimulatorConnection connection = SimulatorSimCallManager.findConnectionByTag(connectionTag); + conferenceables.add(connection); + if (connection.getConference() != null + && !conferenceables.contains(connection.getConference())) { + conferenceables.add(connection.getConference()); + } + } + return conferenceables; + } + + @Nullable + private SimulatorConference findCurrentConference() { + for (String connectionTag : connectionTags) { + SimulatorConnection connection = SimulatorSimCallManager.findConnectionByTag(connectionTag); + if (connection.getConference() != null) { + return (SimulatorConference) connection.getConference(); + } + } + return null; + } + + private static int getCallCount(@NonNull Connection connection) { + return connection.getExtras().getInt(EXTRA_CALL_COUNT); + } + + @Override + public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) { + switch (event.type) { + case Event.NONE: + throw Assert.createIllegalStateFailException(); + case Event.HOLD: + connection.setOnHold(); + break; + case Event.UNHOLD: + connection.setActive(); + break; + case Event.DISCONNECT: + connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); + break; + default: + LogUtil.i( + "SimulatorConferenceCreator.onEvent", "unexpected conference event: " + event.type); + break; + } + } + + @Override + public void onEvent(@NonNull SimulatorConference conference, @NonNull Event event) { + switch (event.type) { + case Event.MERGE: + int capabilities = conference.getConnectionCapabilities(); + capabilities |= Connection.CAPABILITY_SWAP_CONFERENCE; + conference.setConnectionCapabilities(capabilities); + break; + case Event.SEPARATE: + SimulatorConnection connectionToRemove = + SimulatorSimCallManager.findConnectionByTag(event.data1); + conference.removeConnection(connectionToRemove); + break; + case Event.DISCONNECT: + for (Connection connection : new ArrayList<>(conference.getConnections())) { + connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); + } + break; + default: + LogUtil.i( + "SimulatorConferenceCreator.onEvent", "unexpected conference event: " + event.type); + break; + } + } +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnection.java b/java/com/android/dialer/simulator/impl/SimulatorConnection.java index 70c1095dc..e4a34b51b 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorConnection.java +++ b/java/com/android/dialer/simulator/impl/SimulatorConnection.java @@ -41,7 +41,9 @@ public final class SimulatorConnection extends Connection { CAPABILITY_MUTE | CAPABILITY_SUPPORT_HOLD | CAPABILITY_HOLD - | CAPABILITY_CAN_UPGRADE_TO_VIDEO); + | CAPABILITY_CAN_UPGRADE_TO_VIDEO + | CAPABILITY_DISCONNECT_FROM_CONFERENCE + | CAPABILITY_SEPARATE_FROM_CONFERENCE); setVideoProvider(new SimulatorVideoProvider(context, this)); } @@ -108,7 +110,7 @@ public final class SimulatorConnection extends Connection { void onEvent(@NonNull Event event) { events.add(Assert.isNotNull(event)); - for (Listener listener : listeners) { + for (Listener listener : new ArrayList<>(listeners)) { listener.onEvent(this, event); } } diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java index 25d4a7240..465890cf0 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java +++ b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java @@ -109,6 +109,19 @@ public class SimulatorConnectionService extends ConnectionService { return connection; } + @Override + public void onConference(Connection connection1, Connection connection2) { + LogUtil.i( + "SimulatorConnectionService.onConference", + "connection1: " + + SimulatorSimCallManager.getConnectionTag(connection1) + + ", connection2: " + + SimulatorSimCallManager.getConnectionTag(connection2)); + for (Listener listener : listeners) { + listener.onConference((SimulatorConnection) connection1, (SimulatorConnection) connection2); + } + } + private static Uri getPhoneNumber(ConnectionRequest request) { String phoneNumber = request.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER); return Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null); @@ -116,8 +129,11 @@ public class SimulatorConnectionService extends ConnectionService { /** Callback used to notify listeners when a new connection has been added. */ public interface Listener { - void onNewOutgoingConnection(SimulatorConnection connection); + void onNewOutgoingConnection(@NonNull SimulatorConnection connection); + + void onNewIncomingConnection(@NonNull SimulatorConnection connection); - void onNewIncomingConnection(SimulatorConnection connection); + void onConference( + @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2); } } diff --git a/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java b/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java index f85f46602..6d4a26278 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java +++ b/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java @@ -62,6 +62,10 @@ final class SimulatorMissedCallCreator implements SimulatorConnectionService.Lis DISCONNECT_DELAY_MILLIS); } + @Override + public void onConference( + @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) {} + private void addNextIncomingCall(int callCount) { if (callCount <= 0) { LogUtil.i("SimulatorMissedCallCreator.addNextIncomingCall", "done adding calls"); diff --git a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java index 33eac51d1..00899fd69 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java +++ b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java @@ -21,6 +21,7 @@ import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.support.annotation.NonNull; +import android.telecom.Connection; import android.telecom.ConnectionRequest; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; @@ -47,6 +48,7 @@ public class SimulatorSimCallManager { private static final String SIM_CALL_MANAGER_ACCOUNT_ID = "SIMULATOR_ACCOUNT_ID"; private static final String VIDEO_PROVIDER_ACCOUNT_ID = "SIMULATOR_VIDEO_ACCOUNT_ID"; private static final String EXTRA_IS_SIMULATOR_CONNECTION = "is_simulator_connection"; + private static final String EXTRA_CONNECTION_TAG = "connection_tag"; static void register(@NonNull Context context) { LogUtil.enterBlock("SimulatorSimCallManager.register"); @@ -85,9 +87,7 @@ public class SimulatorSimCallManager { register(context); extras = new Bundle(extras); - extras.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true); - String connectionTag = createUniqueConnectionTag(); - extras.putBoolean(connectionTag, true); + extras.putAll(createSimulatorConnectionExtras()); Bundle outgoingCallExtras = new Bundle(); outgoingCallExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras); @@ -102,7 +102,7 @@ public class SimulatorSimCallManager { } catch (SecurityException e) { throw Assert.createIllegalStateFailException("Unable to place call: " + e); } - return connectionTag; + return extras.getString(EXTRA_CONNECTION_TAG); } @NonNull @@ -123,14 +123,12 @@ public class SimulatorSimCallManager { extras = new Bundle(extras); extras.putString(TelephonyManager.EXTRA_INCOMING_NUMBER, callerId); - extras.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true); - String connectionTag = createUniqueConnectionTag(); - extras.putBoolean(connectionTag, true); + extras.putAll(createSimulatorConnectionExtras()); TelecomManager telecomManager = context.getSystemService(TelecomManager.class); telecomManager.addNewIncomingCall( isVideo ? getVideoProviderHandle(context) : getSystemPhoneAccountHandle(context), extras); - return connectionTag; + return extras.getString(EXTRA_CONNECTION_TAG); } @NonNull @@ -167,7 +165,7 @@ public class SimulatorSimCallManager { } @NonNull - private static PhoneAccountHandle getSystemPhoneAccountHandle(@NonNull Context context) { + public static PhoneAccountHandle getSystemPhoneAccountHandle(@NonNull Context context) { TelecomManager telecomManager = context.getSystemService(TelecomManager.class); List handles; try { @@ -189,11 +187,38 @@ public class SimulatorSimCallManager { && request.getExtras().getBoolean(EXTRA_IS_SIMULATOR_CONNECTION); } + @NonNull + public static String getConnectionTag(@NonNull Connection connection) { + String connectionTag = connection.getExtras().getString(EXTRA_CONNECTION_TAG); + return Assert.isNotNull(connectionTag); + } + + @NonNull + public static SimulatorConnection findConnectionByTag(@NonNull String connectionTag) { + Assert.isNotNull(connectionTag); + for (Connection connection : SimulatorConnectionService.getInstance().getAllConnections()) { + if (connection.getExtras().getBoolean(connectionTag)) { + return (SimulatorConnection) connection; + } + } + throw Assert.createIllegalStateFailException(); + } + @NonNull private static String createUniqueConnectionTag() { int callId = new Random().nextInt(); return String.format("simulator_phone_call_%x", Math.abs(callId)); } + @NonNull + static Bundle createSimulatorConnectionExtras() { + Bundle extras = new Bundle(); + extras.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true); + String connectionTag = createUniqueConnectionTag(); + extras.putString(EXTRA_CONNECTION_TAG, connectionTag); + extras.putBoolean(connectionTag, true); + return extras; + } + private SimulatorSimCallManager() {} } diff --git a/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java b/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java index 757658ddf..a843ec03f 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java +++ b/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java @@ -74,6 +74,10 @@ final class SimulatorSpamCallCreator implements SimulatorConnectionService.Liste DISCONNECT_DELAY_MILLIS); } + @Override + public void onConference( + @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) {} + private void addNextIncomingCall(int callCount) { if (callCount <= 0) { LogUtil.i("SimulatorSpamCallCreator.addNextIncomingCall", "done adding calls"); diff --git a/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java b/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java index 3f00ab183..f7256a11c 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java +++ b/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java @@ -109,6 +109,10 @@ final class SimulatorVideoCall } } + @Override + public void onConference( + @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) {} + private boolean isVideoAccountEnabled() { SimulatorSimCallManager.register(context); return context @@ -150,15 +154,12 @@ final class SimulatorVideoCall case Event.DISCONNECT: connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); break; - case Event.STATE_CHANGE: - break; - case Event.DTMF: - break; case Event.SESSION_MODIFY_REQUEST: ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000); break; default: - throw Assert.createIllegalStateFailException(); + LogUtil.i("SimulatorVideoCall.onEvent", "unexpected event: " + event.type); + break; } } } diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java index 8eefb48d9..f478d55d0 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java +++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java @@ -25,6 +25,7 @@ import android.view.ActionProvider; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.ThreadUtil; +import com.android.dialer.simulator.Simulator; import com.android.dialer.simulator.Simulator.Event; /** Entry point in the simulator to create voice calls. */ @@ -37,7 +38,10 @@ final class SimulatorVoiceCall return new SimulatorSubMenu(context) .addItem("Incoming call", () -> new SimulatorVoiceCall(context).addNewIncomingCall(false)) .addItem("Outgoing call", () -> new SimulatorVoiceCall(context).addNewOutgoingCall()) - .addItem("Spam call", () -> new SimulatorVoiceCall(context).addNewIncomingCall(true)); + .addItem("Spam call", () -> new SimulatorVoiceCall(context).addNewIncomingCall(true)) + .addItem( + "GSM conference", + () -> new SimulatorConferenceCreator(context, Simulator.CONFERENCE_TYPE_GSM).start(5)); } private SimulatorVoiceCall(@NonNull Context context) { @@ -62,21 +66,28 @@ final class SimulatorVoiceCall @Override public void onNewOutgoingConnection(@NonNull SimulatorConnection connection) { - if (connection.getExtras().getBoolean(connectionTag)) { + if (isMyConnection(connection)) { LogUtil.i("SimulatorVoiceCall.onNewOutgoingConnection", "connection created"); handleNewConnection(connection); - connection.setActive(); + + // Telecom will force the connection to switch to Dialing when we return it. Wait until after + // we're returned it before changing call state. + ThreadUtil.postOnUiThread(connection::setActive); } } @Override public void onNewIncomingConnection(@NonNull SimulatorConnection connection) { - if (connection.getExtras().getBoolean(connectionTag)) { + if (isMyConnection(connection)) { LogUtil.i("SimulatorVoiceCall.onNewIncomingConnection", "connection created"); handleNewConnection(connection); } } + @Override + public void onConference( + @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) {} + private void handleNewConnection(@NonNull SimulatorConnection connection) { connection.addListener(this); connection.setConnectionCapabilities( @@ -85,6 +96,10 @@ final class SimulatorVoiceCall | Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL); } + private boolean isMyConnection(@NonNull Connection connection) { + return connection.getExtras().getBoolean(connectionTag); + } + @Override public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) { switch (event.type) { @@ -105,15 +120,12 @@ final class SimulatorVoiceCall case Event.DISCONNECT: connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); break; - case Event.STATE_CHANGE: - break; - case Event.DTMF: - break; case Event.SESSION_MODIFY_REQUEST: ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000); break; default: - throw Assert.createIllegalStateFailException(); + LogUtil.i("SimulatorVoiceCall.onEvent", "unexpected event: " + event.type); + break; } } } -- cgit v1.2.3