1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.dialer.simulator.impl;
18 
19 import android.content.Context;
20 import android.os.Bundle;
21 import android.support.annotation.NonNull;
22 import android.telecom.Connection;
23 import android.telecom.DisconnectCause;
24 import com.android.dialer.common.Assert;
25 import com.android.dialer.common.LogUtil;
26 import com.android.dialer.common.concurrent.ThreadUtil;
27 import com.android.dialer.simulator.Simulator;
28 import com.android.dialer.simulator.Simulator.Event;
29 import com.android.dialer.simulator.SimulatorComponent;
30 import com.android.dialer.simulator.SimulatorConnectionsBank;
31 import java.util.ArrayList;
32 import java.util.Locale;
33 
34 /** Creates a conference with a given number of participants. */
35 final class SimulatorConferenceCreator
36     implements SimulatorConnectionService.Listener,
37         SimulatorConnection.Listener,
38         SimulatorConference.Listener {
39   private static final String EXTRA_CALL_COUNT = "call_count";
40   private static final String RECONNECT = "reconnect";
41   @NonNull private final Context context;
42 
43   private final SimulatorConnectionsBank simulatorConnectionsBank;
44 
45   private boolean onNewIncomingConnectionEnabled = false;
46 
47   @Simulator.ConferenceType private final int conferenceType;
48 
SimulatorConferenceCreator( @onNull Context context, @Simulator.ConferenceType int conferenceType)49   SimulatorConferenceCreator(
50       @NonNull Context context, @Simulator.ConferenceType int conferenceType) {
51     this.context = Assert.isNotNull(context);
52     this.conferenceType = conferenceType;
53     simulatorConnectionsBank = SimulatorComponent.get(context).getSimulatorConnectionsBank();
54   }
55 
56   /**
57    * Starts to create certain number of calls to form a conference call.
58    *
59    * @param callCount the number of calls in conference to create.
60    */
start(int callCount)61   void start(int callCount) {
62     onNewIncomingConnectionEnabled = true;
63     SimulatorConnectionService.addListener(this);
64     if (conferenceType == Simulator.CONFERENCE_TYPE_VOLTE) {
65       addNextCall(callCount, true);
66     } else if (conferenceType == Simulator.CONFERENCE_TYPE_GSM) {
67       addNextCall(callCount, false);
68     }
69   }
70   /**
71    * Add a call in a process of making a conference.
72    *
73    * @param callCount the remaining number of calls to make
74    * @param reconnect whether all connections should reconnect once (connections are reconnected
75    *     once in making VoLTE conference)
76    */
addNextCall(int callCount, boolean reconnect)77   private void addNextCall(int callCount, boolean reconnect) {
78     LogUtil.i("SimulatorConferenceCreator.addNextIncomingCall", "callCount: " + callCount);
79     if (callCount <= 0) {
80       LogUtil.i("SimulatorConferenceCreator.addNextCall", "done adding calls");
81       if (reconnect) {
82         simulatorConnectionsBank.disconnectAllConnections();
83         addNextCall(simulatorConnectionsBank.getConnectionTags().size(), false);
84       } else {
85         simulatorConnectionsBank.mergeAllConnections(conferenceType, context);
86         SimulatorConnectionService.removeListener(this);
87       }
88       return;
89     }
90     String callerId = String.format(Locale.US, "+1-650-234%04d", callCount);
91     Bundle extras = new Bundle();
92     extras.putInt(EXTRA_CALL_COUNT, callCount - 1);
93     extras.putBoolean(RECONNECT, reconnect);
94     addConferenceCall(callerId, extras);
95   }
96 
addConferenceCall(String number, Bundle extras)97   private void addConferenceCall(String number, Bundle extras) {
98     switch (conferenceType) {
99       case Simulator.CONFERENCE_TYPE_VOLTE:
100         extras.putBoolean(Simulator.IS_VOLTE, true);
101         break;
102       default:
103         break;
104     }
105     SimulatorSimCallManager.addNewIncomingCall(
106         context, number, SimulatorSimCallManager.CALL_TYPE_VOICE, extras);
107   }
108 
109   @Override
onNewIncomingConnection(@onNull SimulatorConnection connection)110   public void onNewIncomingConnection(@NonNull SimulatorConnection connection) {
111     if (!onNewIncomingConnectionEnabled) {
112       return;
113     }
114     if (!simulatorConnectionsBank.isSimulatorConnection(connection)) {
115       LogUtil.i("SimulatorConferenceCreator.onNewOutgoingConnection", "unknown connection");
116       return;
117     }
118     LogUtil.i("SimulatorConferenceCreator.onNewOutgoingConnection", "connection created");
119     connection.addListener(this);
120     // Once the connection is active, go ahead and conference it and add the next call.
121     ThreadUtil.postDelayedOnUiThread(
122         () -> {
123           connection.setActive();
124           addNextCall(getCallCount(connection), shouldReconnect(connection));
125         },
126         1000);
127   }
128 
129   @Override
onNewOutgoingConnection(@onNull SimulatorConnection connection)130   public void onNewOutgoingConnection(@NonNull SimulatorConnection connection) {}
131 
132   /**
133    * This is called when the user clicks the merge button. We create the initial conference
134    * automatically but with this method we can let the user split and merge calls as desired.
135    */
136   @Override
onConference( @onNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2)137   public void onConference(
138       @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) {
139     LogUtil.enterBlock("SimulatorConferenceCreator.onConference");
140     if (!simulatorConnectionsBank.isSimulatorConnection(connection1)
141         || !simulatorConnectionsBank.isSimulatorConnection(connection2)) {
142       LogUtil.i("SimulatorConferenceCreator.onConference", "unknown connections, ignoring");
143       return;
144     }
145 
146     if (connection1.getConference() != null) {
147       connection1.getConference().addConnection(connection2);
148     } else if (connection2.getConference() != null) {
149       connection2.getConference().addConnection(connection1);
150     } else {
151       SimulatorConference conference =
152           SimulatorConference.newGsmConference(
153               SimulatorSimCallManager.getSystemPhoneAccountHandle(context));
154       conference.addConnection(connection1);
155       conference.addConnection(connection2);
156       conference.addListener(this);
157       SimulatorConnectionService.getInstance().addConference(conference);
158     }
159   }
160 
getCallCount(@onNull Connection connection)161   private static int getCallCount(@NonNull Connection connection) {
162     return connection.getExtras().getInt(EXTRA_CALL_COUNT);
163   }
164 
shouldReconnect(@onNull Connection connection)165   private static boolean shouldReconnect(@NonNull Connection connection) {
166     return connection.getExtras().getBoolean(RECONNECT);
167   }
168 
169   @Override
onEvent(@onNull SimulatorConnection connection, @NonNull Event event)170   public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) {
171     switch (event.type) {
172       case Event.NONE:
173         throw Assert.createIllegalStateFailException();
174       case Event.HOLD:
175         connection.setOnHold();
176         break;
177       case Event.UNHOLD:
178         connection.setActive();
179         break;
180       case Event.DISCONNECT:
181         connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
182         break;
183       default:
184         LogUtil.i(
185             "SimulatorConferenceCreator.onEvent", "unexpected conference event: " + event.type);
186         break;
187     }
188   }
189 
190   @Override
onEvent(@onNull SimulatorConference conference, @NonNull Event event)191   public void onEvent(@NonNull SimulatorConference conference, @NonNull Event event) {
192     switch (event.type) {
193       case Event.MERGE:
194         int capabilities = conference.getConnectionCapabilities();
195         capabilities |= Connection.CAPABILITY_SWAP_CONFERENCE;
196         conference.setConnectionCapabilities(capabilities);
197         break;
198       case Event.SEPARATE:
199         SimulatorConnection connectionToRemove =
200             SimulatorSimCallManager.findConnectionByTag(event.data1);
201         conference.removeConnection(connectionToRemove);
202         break;
203       case Event.DISCONNECT:
204         for (Connection connection : new ArrayList<>(conference.getConnections())) {
205           connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
206         }
207         conference.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
208         break;
209       default:
210         LogUtil.i(
211             "SimulatorConferenceCreator.onEvent", "unexpected conference event: " + event.type);
212         break;
213     }
214   }
215 }
216