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     private final Set<TelephonyConferenceListener> mListeners = Collections.newSetFromMap(
66             new ConcurrentHashMap<>(8, 0.9f, 1));
67 
68     /**
69      * Adds a listener to this conference.
70      * @param listener The listener.
71      */
addTelephonyConferenceListener(@onNull TelephonyConferenceListener listener)72     public void addTelephonyConferenceListener(@NonNull TelephonyConferenceListener listener) {
73         mListeners.add(listener);
74     }
75 
76     /**
77      * Removes a listener from this conference.
78      * @param listener The listener.
79      */
removeTelephonyConferenceListener(@onNull TelephonyConferenceListener listener)80     public void removeTelephonyConferenceListener(@NonNull TelephonyConferenceListener listener) {
81         mListeners.remove(listener);
82     }
83 
84     /**
85      * Constructs a new Conference with a mandatory {@link PhoneAccountHandle}
86      *
87      * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference.
88      */
TelephonyConferenceBase(PhoneAccountHandle phoneAccount)89     public TelephonyConferenceBase(PhoneAccountHandle phoneAccount) {
90         super(phoneAccount);
91     }
92 
93     /**
94      * Adds a connection to this {@link Conference}.
95      * <p>
96      * Should be used in place of {@link Conference#addConnection(Connection)} to ensure
97      * {@link TelephonyConferenceListener}s are informed of the change.
98      *
99      * @param connection The connection.
100      */
addTelephonyConnection(@onNull Connection connection)101     public void addTelephonyConnection(@NonNull Connection connection) {
102         addConnection(connection);
103         notifyConferenceMembershipChanged(connection);
104     }
105 
106     /**
107      * Removes a {@link Connection} from this {@link Conference}.
108      * <p>
109      * Should be used instead of {@link Conference#removeConnection(Connection)} to ensure
110      * {@link TelephonyConferenceListener}s are notified of the change.
111      *
112      * @param connection The connection.
113      */
removeTelephonyConnection(@onNull Connection connection)114     public void removeTelephonyConnection(@NonNull Connection connection) {
115         removeConnection(connection);
116         notifyConferenceMembershipChanged(connection);
117     }
118 
119     /**
120      * Destroys the current {@link Conference} and notifies {@link TelephonyConferenceListener}s of
121      * the change to conference membership.
122      * <p>
123      * Should be used instead of {@link Conference#destroy()} to ensure telephony listeners are
124      * notified.
125      */
destroyTelephonyConference()126     public void destroyTelephonyConference() {
127         // Conference#removeConnection modifies the list of participants, so we need to use an
128         // iterator here to ensure all participants are removed.
129         // Technically Conference#destroy does this, but we want to notify listeners of the state
130         // change so we'll do it here first.
131         Iterator<Connection> connectionIterator = getConnections().iterator();
132         while (connectionIterator.hasNext()) {
133             removeTelephonyConnection(connectionIterator.next());
134         }
135         destroy();
136         notifyDestroyed();
137     }
138 
139     /**
140      * Sets state to be on hold.
141      */
setConferenceOnHold()142      public final void setConferenceOnHold() {
143          int oldState = getState();
144          if (oldState == Connection.STATE_HOLDING) {
145              return;
146          }
147          setOnHold();
148          notifyStateChanged(oldState, getState());
149      }
150 
151      /**
152       * Sets state to be dialing.
153       */
setConferenceOnDialing()154      public final void setConferenceOnDialing() {
155          int oldState = getState();
156          if (oldState == Connection.STATE_DIALING) {
157              return;
158          }
159          setDialing();
160          notifyStateChanged(oldState, getState());
161      }
162 
163      /**
164       * Sets state to be ringing.
165       */
setConferenceOnRinging()166      public final void setConferenceOnRinging() {
167          int oldState = getState();
168          if (oldState == Connection.STATE_RINGING) {
169              return;
170          }
171          setRinging();
172          notifyStateChanged(oldState, getState());
173      }
174 
175      /**
176       * Sets state to be active.
177       */
setConferenceOnActive()178      public final void setConferenceOnActive() {
179          int oldState = getState();
180          if (oldState == Connection.STATE_ACTIVE) {
181              return;
182          }
183          setActive();
184          notifyStateChanged(oldState, getState());
185      }
186 
187     /**
188      * Updates RIL voice radio technology used for current conference after its creation.
189      */
updateCallRadioTechAfterCreation()190     public void updateCallRadioTechAfterCreation() {
191         final Connection primaryConnection = getPrimaryConnection();
192         if (primaryConnection != null && primaryConnection instanceof TelephonyConnection) {
193             TelephonyConnection telephonyConnection = (TelephonyConnection) primaryConnection;
194             Bundle newExtras = new Bundle();
195             newExtras.putInt(TelecomManager.EXTRA_CALL_NETWORK_TYPE,
196                     ServiceState.rilRadioTechnologyToNetworkType(
197                             telephonyConnection.getCallRadioTech()));
198             putExtras(newExtras);
199         } else {
200             Log.w(TAG, "No primary connection found while updateCallRadioTechAfterCreation");
201         }
202     }
203 
204     /**
205      * Removes the specified capability from the set of capabilities of this {@code Conference}.
206      *
207      * @param capability The capability to remove from the set.
208      */
removeCapability(int capability)209     public void removeCapability(int capability) {
210         int newCapabilities = getConnectionCapabilities();
211         newCapabilities &= ~capability;
212 
213         setConnectionCapabilities(newCapabilities);
214     }
215 
216     /**
217      * Adds the specified capability to the set of capabilities of this {@code Conference}.
218      *
219      * @param capability The capability to add to the set.
220      */
addCapability(int capability)221     public void addCapability(int capability) {
222         int newCapabilities = getConnectionCapabilities();
223         newCapabilities |= capability;
224 
225         setConnectionCapabilities(newCapabilities);
226     }
227 
228     /**
229      * Notifies {@link TelephonyConferenceListener}s of a connection being added or removed from
230      * the conference.
231      * @param connection The conference.
232      */
notifyConferenceMembershipChanged(@onNull Connection connection)233     private void notifyConferenceMembershipChanged(@NonNull Connection connection) {
234         for (TelephonyConferenceListener listener : mListeners) {
235             listener.onConferenceMembershipChanged(connection);
236         }
237     }
238 
239     /**
240      * Notifies {@link TelephonyConferenceListener}s of a conference being destroyed
241      */
notifyDestroyed()242     private void notifyDestroyed() {
243         for (TelephonyConferenceListener listener : mListeners) {
244             listener.onDestroyed(this);
245         }
246     }
247 
notifyStateChanged(int oldState, int newState)248     private void notifyStateChanged(int oldState, int newState) {
249         if (oldState != newState) {
250             for (TelephonyConferenceListener listener : mListeners) {
251                 listener.onStateChanged(this, oldState, newState);
252             }
253         }
254     }
255 }
256