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
17package com.android.dialer.simulator.impl;
18
19import android.content.Context;
20import android.os.Bundle;
21import android.support.annotation.NonNull;
22import android.telecom.Connection;
23import android.telecom.DisconnectCause;
24import com.android.dialer.common.Assert;
25import com.android.dialer.common.LogUtil;
26import com.android.dialer.common.concurrent.ThreadUtil;
27import com.android.dialer.simulator.Simulator;
28import com.android.dialer.simulator.Simulator.Event;
29import com.android.dialer.simulator.SimulatorComponent;
30import com.android.dialer.simulator.SimulatorConnectionsBank;
31import java.util.ArrayList;
32import java.util.Locale;
33
34/** Creates a conference with a given number of participants. */
35final 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
49  public 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  void start(int callCount) {
57    onNewIncomingConnectionEnabled = true;
58    SimulatorConnectionService.addListener(this);
59    if (conferenceType == Simulator.CONFERENCE_TYPE_VOLTE) {
60      addNextCall(callCount, true);
61    } else if (conferenceType == Simulator.CONFERENCE_TYPE_GSM) {
62      addNextCall(callCount, false);
63    }
64  }
65  /**
66   * Add a call in a process of making a conference.
67   *
68   * @param callCount the remaining number of calls to make
69   * @param reconnect whether all connections should reconnect once (connections are reconnected
70   *     once in making VoLTE conference)
71   */
72  private void addNextCall(int callCount, boolean reconnect) {
73    LogUtil.i("SimulatorConferenceCreator.addNextIncomingCall", "callCount: " + callCount);
74    if (callCount <= 0) {
75      LogUtil.i("SimulatorConferenceCreator.addNextCall", "done adding calls");
76      if (reconnect) {
77        simulatorConnectionsBank.disconnectAllConnections();
78        addNextCall(simulatorConnectionsBank.getConnectionTags().size(), false);
79      } else {
80        simulatorConnectionsBank.mergeAllConnections(conferenceType, context);
81        SimulatorConnectionService.removeListener(this);
82      }
83      return;
84    }
85    String callerId = String.format(Locale.US, "+1-650-234%04d", callCount);
86    Bundle extras = new Bundle();
87    extras.putInt(EXTRA_CALL_COUNT, callCount - 1);
88    extras.putBoolean(RECONNECT, reconnect);
89    addConferenceCall(callerId, extras);
90  }
91
92  private void addConferenceCall(String number, Bundle extras) {
93    switch (conferenceType) {
94      case Simulator.CONFERENCE_TYPE_VOLTE:
95        extras.putBoolean(Simulator.IS_VOLTE, true);
96        break;
97      default:
98        break;
99    }
100    SimulatorSimCallManager.addNewIncomingCall(
101        context, number, SimulatorSimCallManager.CALL_TYPE_VOICE, extras);
102  }
103
104  @Override
105  public void onNewIncomingConnection(@NonNull SimulatorConnection connection) {
106    if (!onNewIncomingConnectionEnabled) {
107      return;
108    }
109    if (!simulatorConnectionsBank.isSimulatorConnection(connection)) {
110      LogUtil.i("SimulatorConferenceCreator.onNewOutgoingConnection", "unknown connection");
111      return;
112    }
113    LogUtil.i("SimulatorConferenceCreator.onNewOutgoingConnection", "connection created");
114    connection.addListener(this);
115    // Once the connection is active, go ahead and conference it and add the next call.
116    ThreadUtil.postDelayedOnUiThread(
117        () -> {
118          connection.setActive();
119          addNextCall(getCallCount(connection), shouldReconnect(connection));
120        },
121        1000);
122  }
123
124  @Override
125  public void onNewOutgoingConnection(@NonNull SimulatorConnection connection) {}
126
127  /**
128   * This is called when the user clicks the merge button. We create the initial conference
129   * automatically but with this method we can let the user split and merge calls as desired.
130   */
131  @Override
132  public void onConference(
133      @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) {
134    LogUtil.enterBlock("SimulatorConferenceCreator.onConference");
135    if (!simulatorConnectionsBank.isSimulatorConnection(connection1)
136        || !simulatorConnectionsBank.isSimulatorConnection(connection2)) {
137      LogUtil.i("SimulatorConferenceCreator.onConference", "unknown connections, ignoring");
138      return;
139    }
140
141    if (connection1.getConference() != null) {
142      connection1.getConference().addConnection(connection2);
143    } else if (connection2.getConference() != null) {
144      connection2.getConference().addConnection(connection1);
145    } else {
146      SimulatorConference conference =
147          SimulatorConference.newGsmConference(
148              SimulatorSimCallManager.getSystemPhoneAccountHandle(context));
149      conference.addConnection(connection1);
150      conference.addConnection(connection2);
151      conference.addListener(this);
152      SimulatorConnectionService.getInstance().addConference(conference);
153    }
154  }
155
156  private static int getCallCount(@NonNull Connection connection) {
157    return connection.getExtras().getInt(EXTRA_CALL_COUNT);
158  }
159
160  private static boolean shouldReconnect(@NonNull Connection connection) {
161    return connection.getExtras().getBoolean(RECONNECT);
162  }
163
164  @Override
165  public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) {
166    switch (event.type) {
167      case Event.NONE:
168        throw Assert.createIllegalStateFailException();
169      case Event.HOLD:
170        connection.setOnHold();
171        break;
172      case Event.UNHOLD:
173        connection.setActive();
174        break;
175      case Event.DISCONNECT:
176        connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
177        break;
178      default:
179        LogUtil.i(
180            "SimulatorConferenceCreator.onEvent", "unexpected conference event: " + event.type);
181        break;
182    }
183  }
184
185  @Override
186  public void onEvent(@NonNull SimulatorConference conference, @NonNull Event event) {
187    switch (event.type) {
188      case Event.MERGE:
189        int capabilities = conference.getConnectionCapabilities();
190        capabilities |= Connection.CAPABILITY_SWAP_CONFERENCE;
191        conference.setConnectionCapabilities(capabilities);
192        break;
193      case Event.SEPARATE:
194        SimulatorConnection connectionToRemove =
195            SimulatorSimCallManager.findConnectionByTag(event.data1);
196        conference.removeConnection(connectionToRemove);
197        break;
198      case Event.DISCONNECT:
199        for (Connection connection : new ArrayList<>(conference.getConnections())) {
200          connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
201        }
202        conference.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
203        break;
204      default:
205        LogUtil.i(
206            "SimulatorConferenceCreator.onEvent", "unexpected conference event: " + event.type);
207        break;
208    }
209  }
210}
211