ImsConference.java revision cd3b79cc1d3fbb5f77ab17fc2756c75fe7ff9ef7
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
17package com.android.services.telephony;
18
19import android.content.Context;
20import android.graphics.drawable.Icon;
21import android.net.Uri;
22import android.telecom.Conference;
23import android.telecom.ConferenceParticipant;
24import android.telecom.Connection.VideoProvider;
25import android.telecom.Connection;
26import android.telecom.DisconnectCause;
27import android.telecom.Log;
28import android.telecom.PhoneAccountHandle;
29import android.telecom.StatusHints;
30import android.telecom.VideoProfile;
31
32import com.android.internal.telephony.Call;
33import com.android.internal.telephony.CallStateException;
34import com.android.internal.telephony.Phone;
35import com.android.internal.telephony.PhoneConstants;
36import com.android.internal.telephony.imsphone.ImsPhoneConnection;
37import com.android.phone.PhoneUtils;
38import com.android.phone.R;
39
40import java.util.ArrayList;
41import java.util.HashSet;
42import java.util.Iterator;
43import java.util.List;
44import java.util.Map;
45import java.util.concurrent.ConcurrentHashMap;
46
47/**
48 * Represents an IMS conference call.
49 * <p>
50 * An IMS conference call consists of a conference host connection and potentially a list of
51 * conference participants.  The conference host connection represents the radio connection to the
52 * IMS conference server.  Since it is not a connection to any one individual, it is not represented
53 * in Telecom/InCall as a call.  The conference participant information is received via the host
54 * connection via a conference event package.  Conference participant connections do not represent
55 * actual radio connections to the participants; they act as a virtual representation of the
56 * participant, keyed by a unique endpoint {@link android.net.Uri}.
57 * <p>
58 * The {@link ImsConference} listens for conference event package data received via the host
59 * connection and is responsible for managing the conference participant connections which represent
60 * the participants.
61 */
62public class ImsConference extends Conference {
63
64    /**
65     * Listener used to respond to changes to conference participants.  At the conference level we
66     * are most concerned with handling destruction of a conference participant.
67     */
68    private final Connection.Listener mParticipantListener = new Connection.Listener() {
69        /**
70         * Participant has been destroyed.  Remove it from the conference.
71         *
72         * @param connection The participant which was destroyed.
73         */
74        @Override
75        public void onDestroyed(Connection connection) {
76            ConferenceParticipantConnection participant =
77                    (ConferenceParticipantConnection) connection;
78            removeConferenceParticipant(participant);
79            updateManageConference();
80        }
81
82    };
83
84    /**
85     * Listener used to respond to changes to the underlying radio connection for the conference
86     * host connection.  Used to respond to SRVCC changes.
87     */
88    private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
89            new TelephonyConnection.TelephonyConnectionListener() {
90
91        @Override
92        public void onOriginalConnectionConfigured(TelephonyConnection c) {
93            if (c == mConferenceHost) {
94               handleOriginalConnectionChange();
95            }
96        }
97    };
98
99    /**
100     * Listener used to respond to changes to the connection to the IMS conference server.
101     */
102    private final android.telecom.Connection.Listener mConferenceHostListener =
103            new android.telecom.Connection.Listener() {
104
105        /**
106         * Updates the state of the conference based on the new state of the host.
107         *
108         * @param c The host connection.
109         * @param state The new state
110         */
111        @Override
112        public void onStateChanged(android.telecom.Connection c, int state) {
113            setState(state);
114        }
115
116        /**
117         * Disconnects the conference when its host connection disconnects.
118         *
119         * @param c The host connection.
120         * @param disconnectCause The host connection disconnect cause.
121         */
122        @Override
123        public void onDisconnected(android.telecom.Connection c, DisconnectCause disconnectCause) {
124            setDisconnected(disconnectCause);
125        }
126
127        /**
128         * Handles destruction of the host connection; once the host connection has been
129         * destroyed, cleans up the conference participant connection.
130         *
131         * @param connection The host connection.
132         */
133        @Override
134        public void onDestroyed(android.telecom.Connection connection) {
135            disconnectConferenceParticipants();
136        }
137
138        /**
139         * Handles changes to conference participant data as reported by the conference host
140         * connection.
141         *
142         * @param c The connection.
143         * @param participants The participant information.
144         */
145        @Override
146        public void onConferenceParticipantsChanged(android.telecom.Connection c,
147                List<ConferenceParticipant> participants) {
148
149            if (c == null || participants == null) {
150                return;
151            }
152            Log.v(this, "onConferenceParticipantsChanged: %d participants", participants.size());
153            TelephonyConnection telephonyConnection = (TelephonyConnection) c;
154            handleConferenceParticipantsUpdate(telephonyConnection, participants);
155        }
156
157        @Override
158        public void onVideoStateChanged(android.telecom.Connection c, int videoState) {
159            Log.d(this, "onVideoStateChanged video state %d", videoState);
160            setVideoState(c, videoState);
161        }
162
163        @Override
164        public void onVideoProviderChanged(android.telecom.Connection c,
165                Connection.VideoProvider videoProvider) {
166            Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
167                    videoProvider);
168            setVideoProvider(c, videoProvider);
169        }
170
171        @Override
172        public void onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities) {
173            Log.d(this, "onCallCapabilitiesChanged: Connection: %s, callCapabilities: %s", c,
174                    connectionCapabilities);
175            int capabilites = ImsConference.this.getConnectionCapabilities();
176            setConnectionCapabilities(applyVideoCapabilities(capabilites, connectionCapabilities));
177        }
178
179        @Override
180        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
181            Log.v(this, "onStatusHintsChanged");
182            updateStatusHints();
183        }
184    };
185
186    /**
187     * The telephony connection service; used to add new participant connections to Telecom.
188     */
189    private TelephonyConnectionService mTelephonyConnectionService;
190
191    /**
192     * The connection to the conference server which is hosting the conference.
193     */
194    private TelephonyConnection mConferenceHost;
195
196    /**
197     * The known conference participant connections.  The HashMap is keyed by endpoint Uri.
198     * A {@link ConcurrentHashMap} is used as there is a possibility for radio events impacting the
199     * available participants to occur at the same time as an access via the connection service.
200     */
201    private final ConcurrentHashMap<Uri, ConferenceParticipantConnection>
202            mConferenceParticipantConnections =
203                    new ConcurrentHashMap<Uri, ConferenceParticipantConnection>(8, 0.9f, 1);
204
205    public void updateConferenceParticipantsAfterCreation() {
206        if (mConferenceHost != null) {
207            Log.v(this, "updateConferenceStateAfterCreation :: process participant update");
208            handleConferenceParticipantsUpdate(mConferenceHost,
209                    mConferenceHost.getConferenceParticipants());
210        } else {
211            Log.v(this, "updateConferenceStateAfterCreation :: null mConferenceHost");
212        }
213    }
214
215    /**
216     * Initializes a new {@link ImsConference}.
217     *
218     * @param telephonyConnectionService The connection service responsible for adding new
219     *                                   conferene participants.
220     * @param conferenceHost The telephony connection hosting the conference.
221     */
222    public ImsConference(TelephonyConnectionService telephonyConnectionService,
223            TelephonyConnection conferenceHost) {
224
225        super((conferenceHost != null && conferenceHost.getCall() != null &&
226                        conferenceHost.getCall().getPhone() != null) ?
227                PhoneUtils.makePstnPhoneAccountHandle(
228                        conferenceHost.getCall().getPhone()) : null);
229
230        // Specify the connection time of the conference to be the connection time of the original
231        // connection.
232        setConnectTimeMillis(conferenceHost.getOriginalConnection().getConnectTime());
233
234        mTelephonyConnectionService = telephonyConnectionService;
235        setConferenceHost(conferenceHost);
236
237        int capabilities = Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD |
238                Connection.CAPABILITY_MUTE | Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
239
240        capabilities = applyVideoCapabilities(capabilities, mConferenceHost.getConnectionCapabilities());
241        setConnectionCapabilities(capabilities);
242
243    }
244
245    private int applyVideoCapabilities(int conferenceCapabilities, int capabilities) {
246        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) {
247            conferenceCapabilities = applyCapability(conferenceCapabilities,
248                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
249        } else {
250            conferenceCapabilities = removeCapability(conferenceCapabilities,
251                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
252        }
253
254        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL)) {
255            conferenceCapabilities = applyCapability(conferenceCapabilities,
256                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
257        } else {
258            conferenceCapabilities = removeCapability(conferenceCapabilities,
259                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
260        }
261
262        if (can(capabilities, Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
263            conferenceCapabilities = applyCapability(conferenceCapabilities,
264                    Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
265        } else {
266            conferenceCapabilities = removeCapability(conferenceCapabilities,
267                    Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
268        }
269        return conferenceCapabilities;
270    }
271
272    /**
273     * Not used by the IMS conference controller.
274     *
275     * @return {@code Null}.
276     */
277    @Override
278    public android.telecom.Connection getPrimaryConnection() {
279        return null;
280    }
281
282    /**
283     * Returns VideoProvider of the conference. This can be null.
284     *
285     * @hide
286     */
287    @Override
288    public VideoProvider getVideoProvider() {
289        if (mConferenceHost != null) {
290            return mConferenceHost.getVideoProvider();
291        }
292        return null;
293    }
294
295    /**
296     * Returns video state of conference
297     *
298     * @hide
299     */
300    @Override
301    public int getVideoState() {
302        if (mConferenceHost != null) {
303            return mConferenceHost.getVideoState();
304        }
305        return VideoProfile.STATE_AUDIO_ONLY;
306    }
307
308    /**
309     * Invoked when the Conference and all its {@link Connection}s should be disconnected.
310     * <p>
311     * Hangs up the call via the conference host connection.  When the host connection has been
312     * successfully disconnected, the {@link #mConferenceHostListener} listener receives an
313     * {@code onDestroyed} event, which triggers the conference participant connections to be
314     * disconnected.
315     */
316    @Override
317    public void onDisconnect() {
318        Log.v(this, "onDisconnect: hanging up conference host.");
319        if (mConferenceHost == null) {
320            return;
321        }
322
323        Call call = mConferenceHost.getCall();
324        if (call != null) {
325            try {
326                call.hangup();
327            } catch (CallStateException e) {
328                Log.e(this, e, "Exception thrown trying to hangup conference");
329            }
330        }
331    }
332
333    /**
334     * Invoked when the specified {@link android.telecom.Connection} should be separated from the
335     * conference call.
336     * <p>
337     * IMS does not support separating connections from the conference.
338     *
339     * @param connection The connection to separate.
340     */
341    @Override
342    public void onSeparate(android.telecom.Connection connection) {
343        Log.wtf(this, "Cannot separate connections from an IMS conference.");
344    }
345
346    /**
347     * Invoked when the specified {@link android.telecom.Connection} should be merged into the
348     * conference call.
349     *
350     * @param connection The {@code Connection} to merge.
351     */
352    @Override
353    public void onMerge(android.telecom.Connection connection) {
354        try {
355            Phone phone = ((TelephonyConnection) connection).getPhone();
356            if (phone != null) {
357                phone.conference();
358            }
359        } catch (CallStateException e) {
360            Log.e(this, e, "Exception thrown trying to merge call into a conference");
361        }
362    }
363
364    /**
365     * Invoked when the conference should be put on hold.
366     */
367    @Override
368    public void onHold() {
369        if (mConferenceHost == null) {
370            return;
371        }
372        mConferenceHost.performHold();
373    }
374
375    /**
376     * Invoked when the conference should be moved from hold to active.
377     */
378    @Override
379    public void onUnhold() {
380        if (mConferenceHost == null) {
381            return;
382        }
383        mConferenceHost.performUnhold();
384    }
385
386    /**
387     * Invoked to play a DTMF tone.
388     *
389     * @param c A DTMF character.
390     */
391    @Override
392    public void onPlayDtmfTone(char c) {
393        if (mConferenceHost == null) {
394            return;
395        }
396        mConferenceHost.onPlayDtmfTone(c);
397    }
398
399    /**
400     * Invoked to stop playing a DTMF tone.
401     */
402    @Override
403    public void onStopDtmfTone() {
404        if (mConferenceHost == null) {
405            return;
406        }
407        mConferenceHost.onStopDtmfTone();
408    }
409
410    /**
411     * Handles the addition of connections to the {@link ImsConference}.  The
412     * {@link ImsConferenceController} does not add connections to the conference.
413     *
414     * @param connection The newly added connection.
415     */
416    @Override
417    public void onConnectionAdded(android.telecom.Connection connection) {
418        // No-op
419    }
420
421    private int applyCapability(int capabilities, int capability) {
422        int newCapabilities = capabilities | capability;
423        return newCapabilities;
424    }
425
426    private int removeCapability(int capabilities, int capability) {
427        int newCapabilities = capabilities & ~capability;
428        return newCapabilities;
429    }
430
431    /**
432     * Determines if this conference is hosted on the current device or the peer device.
433     *
434     * @return {@code true} if this conference is hosted on the current device, {@code false} if it
435     *      is hosted on the peer device.
436     */
437    public boolean isConferenceHost() {
438        if (mConferenceHost == null) {
439            return false;
440        }
441        com.android.internal.telephony.Connection originalConnection =
442                mConferenceHost.getOriginalConnection();
443        if (!(originalConnection instanceof ImsPhoneConnection)) {
444            return false;
445        }
446
447        ImsPhoneConnection imsPhoneConnection = (ImsPhoneConnection) originalConnection;
448        return imsPhoneConnection.isMultiparty() && imsPhoneConnection.isConferenceHost();
449    }
450
451    /**
452     * Updates the manage conference capability of the conference.  Where there are one or more
453     * conference event package participants, the conference management is permitted.  Where there
454     * are no conference event package participants, conference management is not permitted.
455     * <p>
456     * Note: We add and remove {@link Connection#CAPABILITY_CONFERENCE_HAS_NO_CHILDREN} to ensure
457     * that the conference is represented appropriately on Bluetooth devices.
458     */
459    private void updateManageConference() {
460        boolean couldManageConference = can(Connection.CAPABILITY_MANAGE_CONFERENCE);
461        boolean canManageConference = !mConferenceParticipantConnections.isEmpty();
462        Log.v(this, "updateManageConference was :%s is:%s", couldManageConference ? "Y" : "N",
463                canManageConference ? "Y" : "N");
464
465        if (couldManageConference != canManageConference) {
466            int capabilities = getConnectionCapabilities();
467
468            if (canManageConference) {
469                capabilities |= Connection.CAPABILITY_MANAGE_CONFERENCE;
470                capabilities &= ~Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
471            } else {
472                capabilities &= ~Connection.CAPABILITY_MANAGE_CONFERENCE;
473                capabilities |= Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
474            }
475
476            setConnectionCapabilities(capabilities);
477        }
478    }
479
480    /**
481     * Sets the connection hosting the conference and registers for callbacks.
482     *
483     * @param conferenceHost The connection hosting the conference.
484     */
485    private void setConferenceHost(TelephonyConnection conferenceHost) {
486        if (Log.VERBOSE) {
487            Log.v(this, "setConferenceHost " + conferenceHost);
488        }
489
490        mConferenceHost = conferenceHost;
491        mConferenceHost.addConnectionListener(mConferenceHostListener);
492        mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
493        setState(mConferenceHost.getState());
494        updateStatusHints();
495    }
496
497    /**
498     * Handles state changes for conference participant(s).  The participants data passed in
499     *
500     * @param parent The connection which was notified of the conference participant.
501     * @param participants The conference participant information.
502     */
503    private void handleConferenceParticipantsUpdate(
504            TelephonyConnection parent, List<ConferenceParticipant> participants) {
505
506        if (participants == null) {
507            return;
508        }
509        boolean newParticipantsAdded = false;
510        boolean oldParticipantsRemoved = false;
511        ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
512        HashSet<Uri> participantUserEntities = new HashSet<>(participants.size());
513
514        // Add any new participants and update existing.
515        for (ConferenceParticipant participant : participants) {
516            Uri userEntity = participant.getHandle();
517
518            participantUserEntities.add(userEntity);
519            if (!mConferenceParticipantConnections.containsKey(userEntity)) {
520                createConferenceParticipantConnection(parent, participant);
521                newParticipants.add(participant);
522                newParticipantsAdded = true;
523            } else {
524                ConferenceParticipantConnection connection =
525                        mConferenceParticipantConnections.get(userEntity);
526                connection.updateState(participant.getState());
527            }
528        }
529
530        // Set state of new participants.
531        if (newParticipantsAdded) {
532            // Set the state of the new participants at once and add to the conference
533            for (ConferenceParticipant newParticipant : newParticipants) {
534                ConferenceParticipantConnection connection =
535                        mConferenceParticipantConnections.get(newParticipant.getHandle());
536                connection.updateState(newParticipant.getState());
537            }
538        }
539
540        // Finally, remove any participants from the conference that no longer exist in the
541        // conference event package data.
542        Iterator<Map.Entry<Uri, ConferenceParticipantConnection>> entryIterator =
543                mConferenceParticipantConnections.entrySet().iterator();
544        while (entryIterator.hasNext()) {
545            Map.Entry<Uri, ConferenceParticipantConnection> entry = entryIterator.next();
546
547            if (!participantUserEntities.contains(entry.getKey())) {
548                ConferenceParticipantConnection participant = entry.getValue();
549                participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
550                participant.removeConnectionListener(mParticipantListener);
551                mTelephonyConnectionService.removeConnection(participant);
552                removeConnection(participant);
553                entryIterator.remove();
554                oldParticipantsRemoved = true;
555            }
556        }
557
558        // If new participants were added or old ones were removed, we need to ensure the state of
559        // the manage conference capability is updated.
560        if (newParticipantsAdded || oldParticipantsRemoved) {
561            updateManageConference();
562        }
563    }
564
565    /**
566     * Creates a new {@link ConferenceParticipantConnection} to represent a
567     * {@link ConferenceParticipant}.
568     * <p>
569     * The new connection is added to the conference controller and connection service.
570     *
571     * @param parent The connection which was notified of the participant change (e.g. the
572     *                         parent connection).
573     * @param participant The conference participant information.
574     */
575    private void createConferenceParticipantConnection(
576            TelephonyConnection parent, ConferenceParticipant participant) {
577
578        // Create and add the new connection in holding state so that it does not become the
579        // active call.
580        ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
581                parent.getOriginalConnection(), participant);
582        connection.addConnectionListener(mParticipantListener);
583
584        if (Log.VERBOSE) {
585            Log.v(this, "createConferenceParticipantConnection: %s", connection);
586        }
587
588        mConferenceParticipantConnections.put(participant.getHandle(), connection);
589        PhoneAccountHandle phoneAccountHandle =
590                PhoneUtils.makePstnPhoneAccountHandle(parent.getPhone());
591        mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, connection);
592        addConnection(connection);
593    }
594
595    /**
596     * Removes a conference participant from the conference.
597     *
598     * @param participant The participant to remove.
599     */
600    private void removeConferenceParticipant(ConferenceParticipantConnection participant) {
601        Log.d(this, "removeConferenceParticipant: %s", participant);
602
603        participant.removeConnectionListener(mParticipantListener);
604        mConferenceParticipantConnections.remove(participant.getUserEntity());
605        mTelephonyConnectionService.removeConnection(participant);
606    }
607
608    /**
609     * Disconnects all conference participants from the conference.
610     */
611    private void disconnectConferenceParticipants() {
612        Log.v(this, "disconnectConferenceParticipants");
613
614        for (ConferenceParticipantConnection connection :
615                mConferenceParticipantConnections.values()) {
616
617            connection.removeConnectionListener(mParticipantListener);
618            // Mark disconnect cause as cancelled to ensure that the call is not logged in the
619            // call log.
620            connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
621            mTelephonyConnectionService.removeConnection(connection);
622            connection.destroy();
623        }
624        mConferenceParticipantConnections.clear();
625    }
626
627    /**
628     * Handles a change in the original connection backing the conference host connection.  This can
629     * happen if an SRVCC event occurs on the original IMS connection, requiring a fallback to
630     * GSM or CDMA.
631     * <p>
632     * If this happens, we will add the conference host connection to telecom and tear down the
633     * conference.
634     */
635    private void handleOriginalConnectionChange() {
636        if (mConferenceHost == null) {
637            Log.w(this, "handleOriginalConnectionChange; conference host missing.");
638            return;
639        }
640
641        com.android.internal.telephony.Connection originalConnection =
642                mConferenceHost.getOriginalConnection();
643
644        if (!(originalConnection instanceof ImsPhoneConnection)) {
645            if (Log.VERBOSE) {
646                Log.v(this,
647                        "Original connection for conference host is no longer an IMS connection; " +
648                                "new connection: %s", originalConnection);
649            }
650
651            PhoneAccountHandle phoneAccountHandle =
652                    PhoneUtils.makePstnPhoneAccountHandle(mConferenceHost.getPhone());
653            if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
654                GsmConnection c = new GsmConnection(originalConnection);
655                c.updateState();
656                mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, c);
657                mTelephonyConnectionService.addConnectionToConferenceController(c);
658            } // CDMA case not applicable for SRVCC
659            mConferenceHost.removeConnectionListener(mConferenceHostListener);
660            mConferenceHost.removeTelephonyConnectionListener(mTelephonyConnectionListener);
661            mConferenceHost = null;
662            setDisconnected(new DisconnectCause(DisconnectCause.OTHER));
663            destroy();
664        }
665
666        updateStatusHints();
667    }
668
669    /**
670     * Changes the state of the Ims conference.
671     *
672     * @param state the new state.
673     */
674    public void setState(int state) {
675        Log.v(this, "setState %s", Connection.stateToString(state));
676
677        switch (state) {
678            case Connection.STATE_INITIALIZING:
679            case Connection.STATE_NEW:
680            case Connection.STATE_RINGING:
681                // No-op -- not applicable.
682                break;
683            case Connection.STATE_DIALING:
684                setDialing();
685                break;
686            case Connection.STATE_DISCONNECTED:
687                DisconnectCause disconnectCause;
688                if (mConferenceHost == null) {
689                    disconnectCause = new DisconnectCause(DisconnectCause.CANCELED);
690                } else {
691                    disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause(
692                            mConferenceHost.getOriginalConnection().getDisconnectCause());
693                }
694                setDisconnected(disconnectCause);
695                destroy();
696                break;
697            case Connection.STATE_ACTIVE:
698                setActive();
699                break;
700            case Connection.STATE_HOLDING:
701                setOnHold();
702                break;
703        }
704    }
705
706    private void updateStatusHints() {
707        if (mConferenceHost == null) {
708            setStatusHints(null);
709            return;
710        }
711
712        if (mConferenceHost.isWifi()) {
713            Phone phone = mConferenceHost.getPhone();
714            if (phone != null) {
715                Context context = phone.getContext();
716                setStatusHints(new StatusHints(
717                        context.getString(R.string.status_hint_label_wifi_call),
718                        Icon.createWithResource(
719                                context.getResources(),
720                                R.drawable.ic_signal_wifi_4_bar_24dp),
721                        null /* extras */));
722            }
723        } else {
724            setStatusHints(null);
725        }
726    }
727
728    /**
729     * Builds a string representation of the {@link ImsConference}.
730     *
731     * @return String representing the conference.
732     */
733    public String toString() {
734        StringBuilder sb = new StringBuilder();
735        sb.append("[ImsConference objId:");
736        sb.append(System.identityHashCode(this));
737        sb.append(" state:");
738        sb.append(Connection.stateToString(getState()));
739        sb.append(" hostConnection:");
740        sb.append(mConferenceHost);
741        sb.append(" participants:");
742        sb.append(mConferenceParticipantConnections.size());
743        sb.append("]");
744        return sb.toString();
745    }
746}
747