Home | History | Annotate | Download | only in telecom
      1 /*
      2  * Copyright (C) 2014 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 android.telecom;
     18 
     19 import android.annotation.Nullable;
     20 import android.annotation.SystemApi;
     21 import android.os.Bundle;
     22 import android.telecom.Connection.VideoProvider;
     23 
     24 import java.util.ArrayList;
     25 import java.util.Collections;
     26 import java.util.List;
     27 import java.util.Locale;
     28 import java.util.Set;
     29 import java.util.concurrent.CopyOnWriteArrayList;
     30 import java.util.concurrent.CopyOnWriteArraySet;
     31 
     32 /**
     33  * Represents a conference call which can contain any number of {@link Connection} objects.
     34  */
     35 public abstract class Conference extends Conferenceable {
     36 
     37     /**
     38      * Used to indicate that the conference connection time is not specified.  If not specified,
     39      * Telecom will set the connect time.
     40      */
     41     public static final long CONNECT_TIME_NOT_SPECIFIED = 0;
     42 
     43     /** @hide */
     44     public abstract static class Listener {
     45         public void onStateChanged(Conference conference, int oldState, int newState) {}
     46         public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {}
     47         public void onConnectionAdded(Conference conference, Connection connection) {}
     48         public void onConnectionRemoved(Conference conference, Connection connection) {}
     49         public void onConferenceableConnectionsChanged(
     50                 Conference conference, List<Connection> conferenceableConnections) {}
     51         public void onDestroyed(Conference conference) {}
     52         public void onConnectionCapabilitiesChanged(
     53                 Conference conference, int connectionCapabilities) {}
     54         public void onVideoStateChanged(Conference c, int videoState) { }
     55         public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {}
     56         public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {}
     57         public void onExtrasChanged(Conference conference, Bundle extras) {}
     58     }
     59 
     60     private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
     61     private final List<Connection> mChildConnections = new CopyOnWriteArrayList<>();
     62     private final List<Connection> mUnmodifiableChildConnections =
     63             Collections.unmodifiableList(mChildConnections);
     64     private final List<Connection> mConferenceableConnections = new ArrayList<>();
     65     private final List<Connection> mUnmodifiableConferenceableConnections =
     66             Collections.unmodifiableList(mConferenceableConnections);
     67 
     68     private PhoneAccountHandle mPhoneAccount;
     69     private CallAudioState mCallAudioState;
     70     private int mState = Connection.STATE_NEW;
     71     private DisconnectCause mDisconnectCause;
     72     private int mConnectionCapabilities;
     73     private String mDisconnectMessage;
     74     private long mConnectTimeMillis = CONNECT_TIME_NOT_SPECIFIED;
     75     private StatusHints mStatusHints;
     76     private Bundle mExtras;
     77 
     78     private final Connection.Listener mConnectionDeathListener = new Connection.Listener() {
     79         @Override
     80         public void onDestroyed(Connection c) {
     81             if (mConferenceableConnections.remove(c)) {
     82                 fireOnConferenceableConnectionsChanged();
     83             }
     84         }
     85     };
     86 
     87     /**
     88      * Constructs a new Conference with a mandatory {@link PhoneAccountHandle}
     89      *
     90      * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference.
     91      */
     92     public Conference(PhoneAccountHandle phoneAccount) {
     93         mPhoneAccount = phoneAccount;
     94     }
     95 
     96     /**
     97      * Returns the {@link PhoneAccountHandle} the conference call is being placed through.
     98      *
     99      * @return A {@code PhoneAccountHandle} object representing the PhoneAccount of the conference.
    100      */
    101     public final PhoneAccountHandle getPhoneAccountHandle() {
    102         return mPhoneAccount;
    103     }
    104 
    105     /**
    106      * Returns the list of connections currently associated with the conference call.
    107      *
    108      * @return A list of {@code Connection} objects which represent the children of the conference.
    109      */
    110     public final List<Connection> getConnections() {
    111         return mUnmodifiableChildConnections;
    112     }
    113 
    114     /**
    115      * Gets the state of the conference call. See {@link Connection} for valid values.
    116      *
    117      * @return A constant representing the state the conference call is currently in.
    118      */
    119     public final int getState() {
    120         return mState;
    121     }
    122 
    123     /**
    124      * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class
    125      * {@link Connection} for valid values.
    126      *
    127      * @return A bitmask of the capabilities of the conference call.
    128      */
    129     public final int getConnectionCapabilities() {
    130         return mConnectionCapabilities;
    131     }
    132 
    133     /**
    134      * Whether the given capabilities support the specified capability.
    135      *
    136      * @param capabilities A capability bit field.
    137      * @param capability The capability to check capabilities for.
    138      * @return Whether the specified capability is supported.
    139      * @hide
    140      */
    141     public static boolean can(int capabilities, int capability) {
    142         return (capabilities & capability) != 0;
    143     }
    144 
    145     /**
    146      * Whether the capabilities of this {@code Connection} supports the specified capability.
    147      *
    148      * @param capability The capability to check capabilities for.
    149      * @return Whether the specified capability is supported.
    150      * @hide
    151      */
    152     public boolean can(int capability) {
    153         return can(mConnectionCapabilities, capability);
    154     }
    155 
    156     /**
    157      * Removes the specified capability from the set of capabilities of this {@code Conference}.
    158      *
    159      * @param capability The capability to remove from the set.
    160      * @hide
    161      */
    162     public void removeCapability(int capability) {
    163         mConnectionCapabilities &= ~capability;
    164     }
    165 
    166     /**
    167      * Adds the specified capability to the set of capabilities of this {@code Conference}.
    168      *
    169      * @param capability The capability to add to the set.
    170      * @hide
    171      */
    172     public void addCapability(int capability) {
    173         mConnectionCapabilities |= capability;
    174     }
    175 
    176     /**
    177      * @return The audio state of the conference, describing how its audio is currently
    178      *         being routed by the system. This is {@code null} if this Conference
    179      *         does not directly know about its audio state.
    180      * @deprecated Use {@link #getCallAudioState()} instead.
    181      * @hide
    182      */
    183     @Deprecated
    184     @SystemApi
    185     public final AudioState getAudioState() {
    186         return new AudioState(mCallAudioState);
    187     }
    188 
    189     /**
    190      * @return The audio state of the conference, describing how its audio is currently
    191      *         being routed by the system. This is {@code null} if this Conference
    192      *         does not directly know about its audio state.
    193      */
    194     public final CallAudioState getCallAudioState() {
    195         return mCallAudioState;
    196     }
    197 
    198     /**
    199      * Returns VideoProvider of the primary call. This can be null.
    200      */
    201     public VideoProvider getVideoProvider() {
    202         return null;
    203     }
    204 
    205     /**
    206      * Returns video state of the primary call.
    207      */
    208     public int getVideoState() {
    209         return VideoProfile.STATE_AUDIO_ONLY;
    210     }
    211 
    212     /**
    213      * Invoked when the Conference and all it's {@link Connection}s should be disconnected.
    214      */
    215     public void onDisconnect() {}
    216 
    217     /**
    218      * Invoked when the specified {@link Connection} should be separated from the conference call.
    219      *
    220      * @param connection The connection to separate.
    221      */
    222     public void onSeparate(Connection connection) {}
    223 
    224     /**
    225      * Invoked when the specified {@link Connection} should merged with the conference call.
    226      *
    227      * @param connection The {@code Connection} to merge.
    228      */
    229     public void onMerge(Connection connection) {}
    230 
    231     /**
    232      * Invoked when the conference should be put on hold.
    233      */
    234     public void onHold() {}
    235 
    236     /**
    237      * Invoked when the conference should be moved from hold to active.
    238      */
    239     public void onUnhold() {}
    240 
    241     /**
    242      * Invoked when the child calls should be merged. Only invoked if the conference contains the
    243      * capability {@link Connection#CAPABILITY_MERGE_CONFERENCE}.
    244      */
    245     public void onMerge() {}
    246 
    247     /**
    248      * Invoked when the child calls should be swapped. Only invoked if the conference contains the
    249      * capability {@link Connection#CAPABILITY_SWAP_CONFERENCE}.
    250      */
    251     public void onSwap() {}
    252 
    253     /**
    254      * Notifies this conference of a request to play a DTMF tone.
    255      *
    256      * @param c A DTMF character.
    257      */
    258     public void onPlayDtmfTone(char c) {}
    259 
    260     /**
    261      * Notifies this conference of a request to stop any currently playing DTMF tones.
    262      */
    263     public void onStopDtmfTone() {}
    264 
    265     /**
    266      * Notifies this conference that the {@link #getAudioState()} property has a new value.
    267      *
    268      * @param state The new call audio state.
    269      * @deprecated Use {@link #onCallAudioStateChanged(CallAudioState)} instead.
    270      * @hide
    271      */
    272     @SystemApi
    273     @Deprecated
    274     public void onAudioStateChanged(AudioState state) {}
    275 
    276     /**
    277      * Notifies this conference that the {@link #getCallAudioState()} property has a new value.
    278      *
    279      * @param state The new call audio state.
    280      */
    281     public void onCallAudioStateChanged(CallAudioState state) {}
    282 
    283     /**
    284      * Notifies this conference that a connection has been added to it.
    285      *
    286      * @param connection The newly added connection.
    287      */
    288     public void onConnectionAdded(Connection connection) {}
    289 
    290     /**
    291      * Sets state to be on hold.
    292      */
    293     public final void setOnHold() {
    294         setState(Connection.STATE_HOLDING);
    295     }
    296 
    297     /**
    298      * Sets state to be dialing.
    299      */
    300     public final void setDialing() {
    301         setState(Connection.STATE_DIALING);
    302     }
    303 
    304     /**
    305      * Sets state to be active.
    306      */
    307     public final void setActive() {
    308         setState(Connection.STATE_ACTIVE);
    309     }
    310 
    311     /**
    312      * Sets state to disconnected.
    313      *
    314      * @param disconnectCause The reason for the disconnection, as described by
    315      *     {@link android.telecom.DisconnectCause}.
    316      */
    317     public final void setDisconnected(DisconnectCause disconnectCause) {
    318         mDisconnectCause = disconnectCause;;
    319         setState(Connection.STATE_DISCONNECTED);
    320         for (Listener l : mListeners) {
    321             l.onDisconnected(this, mDisconnectCause);
    322         }
    323     }
    324 
    325     /**
    326      * @return The {@link DisconnectCause} for this connection.
    327      */
    328     public final DisconnectCause getDisconnectCause() {
    329         return mDisconnectCause;
    330     }
    331 
    332     /**
    333      * Sets the capabilities of a conference. See {@code CAPABILITY_*} constants of class
    334      * {@link Connection} for valid values.
    335      *
    336      * @param connectionCapabilities A bitmask of the {@code PhoneCapabilities} of the conference call.
    337      */
    338     public final void setConnectionCapabilities(int connectionCapabilities) {
    339         if (connectionCapabilities != mConnectionCapabilities) {
    340             mConnectionCapabilities = connectionCapabilities;
    341 
    342             for (Listener l : mListeners) {
    343                 l.onConnectionCapabilitiesChanged(this, mConnectionCapabilities);
    344             }
    345         }
    346     }
    347 
    348     /**
    349      * Adds the specified connection as a child of this conference.
    350      *
    351      * @param connection The connection to add.
    352      * @return True if the connection was successfully added.
    353      */
    354     public final boolean addConnection(Connection connection) {
    355         Log.d(this, "Connection=%s, connection=", connection);
    356         if (connection != null && !mChildConnections.contains(connection)) {
    357             if (connection.setConference(this)) {
    358                 mChildConnections.add(connection);
    359                 onConnectionAdded(connection);
    360                 for (Listener l : mListeners) {
    361                     l.onConnectionAdded(this, connection);
    362                 }
    363                 return true;
    364             }
    365         }
    366         return false;
    367     }
    368 
    369     /**
    370      * Removes the specified connection as a child of this conference.
    371      *
    372      * @param connection The connection to remove.
    373      */
    374     public final void removeConnection(Connection connection) {
    375         Log.d(this, "removing %s from %s", connection, mChildConnections);
    376         if (connection != null && mChildConnections.remove(connection)) {
    377             connection.resetConference();
    378             for (Listener l : mListeners) {
    379                 l.onConnectionRemoved(this, connection);
    380             }
    381         }
    382     }
    383 
    384     /**
    385      * Sets the connections with which this connection can be conferenced.
    386      *
    387      * @param conferenceableConnections The set of connections this connection can conference with.
    388      */
    389     public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
    390         clearConferenceableList();
    391         for (Connection c : conferenceableConnections) {
    392             // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
    393             // small amount of items here.
    394             if (!mConferenceableConnections.contains(c)) {
    395                 c.addConnectionListener(mConnectionDeathListener);
    396                 mConferenceableConnections.add(c);
    397             }
    398         }
    399         fireOnConferenceableConnectionsChanged();
    400     }
    401 
    402     /**
    403      * Set the video state for the conference.
    404      * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY},
    405      * {@link VideoProfile#STATE_BIDIRECTIONAL},
    406      * {@link VideoProfile#STATE_TX_ENABLED},
    407      * {@link VideoProfile#STATE_RX_ENABLED}.
    408      *
    409      * @param videoState The new video state.
    410      */
    411     public final void setVideoState(Connection c, int videoState) {
    412         Log.d(this, "setVideoState Conference: %s Connection: %s VideoState: %s",
    413                 this, c, videoState);
    414         for (Listener l : mListeners) {
    415             l.onVideoStateChanged(this, videoState);
    416         }
    417     }
    418 
    419     /**
    420      * Sets the video connection provider.
    421      *
    422      * @param videoProvider The video provider.
    423      */
    424     public final void setVideoProvider(Connection c, Connection.VideoProvider videoProvider) {
    425         Log.d(this, "setVideoProvider Conference: %s Connection: %s VideoState: %s",
    426                 this, c, videoProvider);
    427         for (Listener l : mListeners) {
    428             l.onVideoProviderChanged(this, videoProvider);
    429         }
    430     }
    431 
    432     private final void fireOnConferenceableConnectionsChanged() {
    433         for (Listener l : mListeners) {
    434             l.onConferenceableConnectionsChanged(this, getConferenceableConnections());
    435         }
    436     }
    437 
    438     /**
    439      * Returns the connections with which this connection can be conferenced.
    440      */
    441     public final List<Connection> getConferenceableConnections() {
    442         return mUnmodifiableConferenceableConnections;
    443     }
    444 
    445     /**
    446      * Tears down the conference object and any of its current connections.
    447      */
    448     public final void destroy() {
    449         Log.d(this, "destroying conference : %s", this);
    450         // Tear down the children.
    451         for (Connection connection : mChildConnections) {
    452             Log.d(this, "removing connection %s", connection);
    453             removeConnection(connection);
    454         }
    455 
    456         // If not yet disconnected, set the conference call as disconnected first.
    457         if (mState != Connection.STATE_DISCONNECTED) {
    458             Log.d(this, "setting to disconnected");
    459             setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
    460         }
    461 
    462         // ...and notify.
    463         for (Listener l : mListeners) {
    464             l.onDestroyed(this);
    465         }
    466     }
    467 
    468     /**
    469      * Add a listener to be notified of a state change.
    470      *
    471      * @param listener The new listener.
    472      * @return This conference.
    473      * @hide
    474      */
    475     public final Conference addListener(Listener listener) {
    476         mListeners.add(listener);
    477         return this;
    478     }
    479 
    480     /**
    481      * Removes the specified listener.
    482      *
    483      * @param listener The listener to remove.
    484      * @return This conference.
    485      * @hide
    486      */
    487     public final Conference removeListener(Listener listener) {
    488         mListeners.remove(listener);
    489         return this;
    490     }
    491 
    492     /**
    493      * Retrieves the primary connection associated with the conference.  The primary connection is
    494      * the connection from which the conference will retrieve its current state.
    495      *
    496      * @return The primary connection.
    497      * @hide
    498      */
    499     @SystemApi
    500     public Connection getPrimaryConnection() {
    501         if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) {
    502             return null;
    503         }
    504         return mUnmodifiableChildConnections.get(0);
    505     }
    506 
    507     /**
    508      * @hide
    509      * @deprecated Use {@link #setConnectionTime}.
    510      */
    511     @Deprecated
    512     @SystemApi
    513     public final void setConnectTimeMillis(long connectTimeMillis) {
    514         setConnectionTime(connectTimeMillis);
    515     }
    516 
    517     /**
    518      * Sets the connection start time of the {@code Conference}.
    519      *
    520      * @param connectionTimeMillis The connection time, in milliseconds.
    521      */
    522     public final void setConnectionTime(long connectionTimeMillis) {
    523         mConnectTimeMillis = connectionTimeMillis;
    524     }
    525 
    526     /**
    527      * @hide
    528      * @deprecated Use {@link #getConnectionTime}.
    529      */
    530     @Deprecated
    531     @SystemApi
    532     public final long getConnectTimeMillis() {
    533         return getConnectionTime();
    534     }
    535 
    536     /**
    537      * Retrieves the connection start time of the {@code Conference}, if specified.  A value of
    538      * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time
    539      * of the conference.
    540      *
    541      * @return The time at which the {@code Conference} was connected.
    542      */
    543     public final long getConnectionTime() {
    544         return mConnectTimeMillis;
    545     }
    546 
    547     /**
    548      * Inform this Conference that the state of its audio output has been changed externally.
    549      *
    550      * @param state The new audio state.
    551      * @hide
    552      */
    553     final void setCallAudioState(CallAudioState state) {
    554         Log.d(this, "setCallAudioState %s", state);
    555         mCallAudioState = state;
    556         onAudioStateChanged(getAudioState());
    557         onCallAudioStateChanged(state);
    558     }
    559 
    560     private void setState(int newState) {
    561         if (newState != Connection.STATE_ACTIVE &&
    562                 newState != Connection.STATE_HOLDING &&
    563                 newState != Connection.STATE_DISCONNECTED) {
    564             Log.w(this, "Unsupported state transition for Conference call.",
    565                     Connection.stateToString(newState));
    566             return;
    567         }
    568 
    569         if (mState != newState) {
    570             int oldState = mState;
    571             mState = newState;
    572             for (Listener l : mListeners) {
    573                 l.onStateChanged(this, oldState, newState);
    574             }
    575         }
    576     }
    577 
    578     private final void clearConferenceableList() {
    579         for (Connection c : mConferenceableConnections) {
    580             c.removeConnectionListener(mConnectionDeathListener);
    581         }
    582         mConferenceableConnections.clear();
    583     }
    584 
    585     @Override
    586     public String toString() {
    587         return String.format(Locale.US,
    588                 "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]",
    589                 Connection.stateToString(mState),
    590                 Call.Details.capabilitiesToString(mConnectionCapabilities),
    591                 getVideoState(),
    592                 getVideoProvider(),
    593                 super.toString());
    594     }
    595 
    596     /**
    597      * Sets the label and icon status to display in the InCall UI.
    598      *
    599      * @param statusHints The status label and icon to set.
    600      */
    601     public final void setStatusHints(StatusHints statusHints) {
    602         mStatusHints = statusHints;
    603         for (Listener l : mListeners) {
    604             l.onStatusHintsChanged(this, statusHints);
    605         }
    606     }
    607 
    608     /**
    609      * @return The status hints for this conference.
    610      */
    611     public final StatusHints getStatusHints() {
    612         return mStatusHints;
    613     }
    614 
    615     /**
    616      * Set some extras that can be associated with this {@code Conference}. No assumptions should
    617      * be made as to how an In-Call UI or service will handle these extras.
    618      * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
    619      *
    620      * @param extras The extras associated with this {@code Connection}.
    621      */
    622     public final void setExtras(@Nullable Bundle extras) {
    623         mExtras = extras;
    624         for (Listener l : mListeners) {
    625             l.onExtrasChanged(this, extras);
    626         }
    627     }
    628 
    629     /**
    630      * @return The extras associated with this conference.
    631      */
    632     public final Bundle getExtras() {
    633         return mExtras;
    634     }
    635 }
    636