1 /*
2  * Copyright (C) 2019 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.services.telephony;
18 
19 import android.annotation.NonNull;
20 import android.os.Bundle;
21 import android.telecom.Conference;
22 import android.telecom.Connection;
23 import android.telecom.PhoneAccountHandle;
24 import android.telecom.TelecomManager;
25 import android.telephony.ServiceState;
26 
27 import java.util.Collections;
28 import java.util.Iterator;
29 import java.util.Set;
30 import java.util.concurrent.ConcurrentHashMap;
31 
32 /**
33  * Base class for the various Telephony {@link Conference} implementations ({@link CdmaConference},
34  * {@link TelephonyConference}, and {@link ImsConference}).  Adds some common listener code which
35  * all of these conferences use.
36  */
37 public class TelephonyConferenceBase extends Conference {
38     private static final String TAG = "TelephonyConferenceBase";
39 
40     /**
41      * Listener for conference events.
42      */
43     public abstract static class TelephonyConferenceListener {
44         /**
45          * Listener called when a connection is added or removed from a conference.
46          * @param connection The connection.
47          */
onConferenceMembershipChanged(Connection connection)48         public void onConferenceMembershipChanged(Connection connection) {}
49 
50         /**
51          * Listener called when there conference call state changes.
52          * @param conference The conference.
53          * @param oldState previous state of conference call.
54          * @param newState new state of conference call.
55          */
onStateChanged(Conference conference, int oldState, int newState)56         public void onStateChanged(Conference conference, int oldState, int newState) {}
57 
58         /**
59          * Listener called when a conference is destroyed.
60          * @param conference The conference.
61          */
onDestroyed(Conference conference)62         public void onDestroyed(Conference conference) {}
63 
64         /**
65          * Listener called when a conference either reaches capacity or is no longer at capacity.
66          */
onConferenceCapacityChanged()67         public void onConferenceCapacityChanged() {}
68     }
69 
70     private final Set<TelephonyConferenceListener> mListeners = Collections.newSetFromMap(
71             new ConcurrentHashMap<>(8, 0.9f, 1));
72 
73     /**
74      * Adds a listener to this conference.
75      * @param listener The listener.
76      */
addTelephonyConferenceListener(@onNull TelephonyConferenceListener listener)77     public void addTelephonyConferenceListener(@NonNull TelephonyConferenceListener listener) {
78         mListeners.add(listener);
79     }
80 
81     /**
82      * Removes a listener from this conference.
83      * @param listener The listener.
84      */
removeTelephonyConferenceListener(@onNull TelephonyConferenceListener listener)85     public void removeTelephonyConferenceListener(@NonNull TelephonyConferenceListener listener) {
86         mListeners.remove(listener);
87     }
88 
89     /**
90      * Constructs a new Conference with a mandatory {@link PhoneAccountHandle}
91      *
92      * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference.
93      */
TelephonyConferenceBase(PhoneAccountHandle phoneAccount)94     public TelephonyConferenceBase(PhoneAccountHandle phoneAccount) {
95         super(phoneAccount);
96     }
97 
98     /**
99      * Adds a connection to this {@link Conference}.
100      * <p>
101      * Should be used in place of {@link Conference#addConnection(Connection)} to ensure
102      * {@link TelephonyConferenceListener}s are informed of the change.
103      *
104      * @param connection The connection.
105      */
addTelephonyConnection(@onNull Connection connection)106     public void addTelephonyConnection(@NonNull Connection connection) {
107         addConnection(connection);
108         notifyConferenceMembershipChanged(connection);
109     }
110 
111     /**
112      * Removes a {@link Connection} from this {@link Conference}.
113      * <p>
114      * Should be used instead of {@link Conference#removeConnection(Connection)} to ensure
115      * {@link TelephonyConferenceListener}s are notified of the change.
116      *
117      * @param connection The connection.
118      */
removeTelephonyConnection(@onNull Connection connection)119     public void removeTelephonyConnection(@NonNull Connection connection) {
120         removeConnection(connection);
121         notifyConferenceMembershipChanged(connection);
122     }
123 
124     /**
125      * Destroys the current {@link Conference} and notifies {@link TelephonyConferenceListener}s of
126      * the change to conference membership.
127      * <p>
128      * Should be used instead of {@link Conference#destroy()} to ensure telephony listeners are
129      * notified.
130      */
destroyTelephonyConference()131     public void destroyTelephonyConference() {
132         // Conference#removeConnection modifies the list of participants, so we need to use an
133         // iterator here to ensure all participants are removed.
134         // Technically Conference#destroy does this, but we want to notify listeners of the state
135         // change so we'll do it here first.
136         Iterator<Connection> connectionIterator = getConnections().iterator();
137         while (connectionIterator.hasNext()) {
138             removeTelephonyConnection(connectionIterator.next());
139         }
140         destroy();
141         notifyDestroyed();
142     }
143 
144     /**
145      * Sets state to be on hold.
146      */
setConferenceOnHold()147      public final void setConferenceOnHold() {
148          int oldState = getState();
149          if (oldState == Connection.STATE_HOLDING) {
150              return;
151          }
152          setOnHold();
153          notifyStateChanged(oldState, getState());
154      }
155 
156      /**
157       * Sets state to be dialing.
158       */
setConferenceOnDialing()159      public final void setConferenceOnDialing() {
160          int oldState = getState();
161          if (oldState == Connection.STATE_DIALING) {
162              return;
163          }
164          setDialing();
165          notifyStateChanged(oldState, getState());
166      }
167 
168      /**
169       * Sets state to be ringing.
170       */
setConferenceOnRinging()171      public final void setConferenceOnRinging() {
172          int oldState = getState();
173          if (oldState == Connection.STATE_RINGING) {
174              return;
175          }
176          setRinging();
177          notifyStateChanged(oldState, getState());
178      }
179 
180      /**
181       * Sets state to be active.
182       */
setConferenceOnActive()183      public final void setConferenceOnActive() {
184          int oldState = getState();
185          if (oldState == Connection.STATE_ACTIVE) {
186              return;
187          }
188          setActive();
189          notifyStateChanged(oldState, getState());
190      }
191 
192     /**
193      * Updates RIL voice radio technology used for current conference after its creation.
194      */
updateCallRadioTechAfterCreation()195     public void updateCallRadioTechAfterCreation() {
196         final Connection primaryConnection = getPrimaryConnection();
197         if (primaryConnection != null && primaryConnection instanceof TelephonyConnection) {
198             TelephonyConnection telephonyConnection = (TelephonyConnection) primaryConnection;
199             Bundle newExtras = new Bundle();
200             newExtras.putInt(TelecomManager.EXTRA_CALL_NETWORK_TYPE,
201                     ServiceState.rilRadioTechnologyToNetworkType(
202                             telephonyConnection.getCallRadioTech()));
203             putExtras(newExtras);
204         } else {
205             Log.w(TAG, "No primary connection found while updateCallRadioTechAfterCreation");
206         }
207     }
208 
209     /**
210      * Removes the specified capability from the set of capabilities of this {@code Conference}.
211      *
212      * @param capability The capability to remove from the set.
213      */
removeCapability(int capability)214     public void removeCapability(int capability) {
215         int newCapabilities = getConnectionCapabilities();
216         newCapabilities &= ~capability;
217 
218         setConnectionCapabilities(newCapabilities);
219     }
220 
221     /**
222      * Adds the specified capability to the set of capabilities of this {@code Conference}.
223      *
224      * @param capability The capability to add to the set.
225      */
addCapability(int capability)226     public void addCapability(int capability) {
227         int newCapabilities = getConnectionCapabilities();
228         newCapabilities |= capability;
229 
230         setConnectionCapabilities(newCapabilities);
231     }
232 
233     /**
234      * Notifies {@link TelephonyConferenceListener}s of a connection being added or removed from
235      * the conference.
236      * @param connection The conference.
237      */
notifyConferenceMembershipChanged(@onNull Connection connection)238     private void notifyConferenceMembershipChanged(@NonNull Connection connection) {
239         for (TelephonyConferenceListener listener : mListeners) {
240             listener.onConferenceMembershipChanged(connection);
241         }
242     }
243 
244     /**
245      * Notifies the {@link TelephonyConferenceListener}s when the capacity of the conference has
246      * changed.
247      */
notifyConferenceCapacityChanged()248     public void notifyConferenceCapacityChanged() {
249         mListeners.forEach(l -> l.onConferenceCapacityChanged());
250     }
251 
252     /**
253      * Notifies {@link TelephonyConferenceListener}s of a conference being destroyed
254      */
notifyDestroyed()255     private void notifyDestroyed() {
256         for (TelephonyConferenceListener listener : mListeners) {
257             listener.onDestroyed(this);
258         }
259     }
260 
notifyStateChanged(int oldState, int newState)261     private void notifyStateChanged(int oldState, int newState) {
262         if (oldState != newState) {
263             for (TelephonyConferenceListener listener : mListeners) {
264                 listener.onStateChanged(this, oldState, newState);
265             }
266         }
267     }
268 }
269