ImsConference.java revision f789e6e1f9e67e26d13305c494f00254b92f63f3
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        long connectTime = conferenceHost.getOriginalConnection().getConnectTime();
233        setConnectTimeMillis(connectTime);
234        // Set the connectTime in the connection as well.
235        conferenceHost.setConnectTimeMillis(connectTime);
236
237        mTelephonyConnectionService = telephonyConnectionService;
238        setConferenceHost(conferenceHost);
239
240        int capabilities = Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD |
241                Connection.CAPABILITY_MUTE | Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
242
243        capabilities = applyVideoCapabilities(capabilities, mConferenceHost.getConnectionCapabilities());
244        setConnectionCapabilities(capabilities);
245
246    }
247
248    private int applyVideoCapabilities(int conferenceCapabilities, int capabilities) {
249        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) {
250            conferenceCapabilities = applyCapability(conferenceCapabilities,
251                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
252        } else {
253            conferenceCapabilities = removeCapability(conferenceCapabilities,
254                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
255        }
256
257        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL)) {
258            conferenceCapabilities = applyCapability(conferenceCapabilities,
259                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
260        } else {
261            conferenceCapabilities = removeCapability(conferenceCapabilities,
262                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
263        }
264
265        if (can(capabilities, Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
266            conferenceCapabilities = applyCapability(conferenceCapabilities,
267                    Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
268        } else {
269            conferenceCapabilities = removeCapability(conferenceCapabilities,
270                    Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
271        }
272        return conferenceCapabilities;
273    }
274
275    /**
276     * Not used by the IMS conference controller.
277     *
278     * @return {@code Null}.
279     */
280    @Override
281    public android.telecom.Connection getPrimaryConnection() {
282        return null;
283    }
284
285    /**
286     * Returns VideoProvider of the conference. This can be null.
287     *
288     * @hide
289     */
290    @Override
291    public VideoProvider getVideoProvider() {
292        if (mConferenceHost != null) {
293            return mConferenceHost.getVideoProvider();
294        }
295        return null;
296    }
297
298    /**
299     * Returns video state of conference
300     *
301     * @hide
302     */
303    @Override
304    public int getVideoState() {
305        if (mConferenceHost != null) {
306            return mConferenceHost.getVideoState();
307        }
308        return VideoProfile.STATE_AUDIO_ONLY;
309    }
310
311    /**
312     * Invoked when the Conference and all its {@link Connection}s should be disconnected.
313     * <p>
314     * Hangs up the call via the conference host connection.  When the host connection has been
315     * successfully disconnected, the {@link #mConferenceHostListener} listener receives an
316     * {@code onDestroyed} event, which triggers the conference participant connections to be
317     * disconnected.
318     */
319    @Override
320    public void onDisconnect() {
321        Log.v(this, "onDisconnect: hanging up conference host.");
322        if (mConferenceHost == null) {
323            return;
324        }
325
326        Call call = mConferenceHost.getCall();
327        if (call != null) {
328            try {
329                call.hangup();
330            } catch (CallStateException e) {
331                Log.e(this, e, "Exception thrown trying to hangup conference");
332            }
333        }
334    }
335
336    /**
337     * Invoked when the specified {@link android.telecom.Connection} should be separated from the
338     * conference call.
339     * <p>
340     * IMS does not support separating connections from the conference.
341     *
342     * @param connection The connection to separate.
343     */
344    @Override
345    public void onSeparate(android.telecom.Connection connection) {
346        Log.wtf(this, "Cannot separate connections from an IMS conference.");
347    }
348
349    /**
350     * Invoked when the specified {@link android.telecom.Connection} should be merged into the
351     * conference call.
352     *
353     * @param connection The {@code Connection} to merge.
354     */
355    @Override
356    public void onMerge(android.telecom.Connection connection) {
357        try {
358            Phone phone = ((TelephonyConnection) connection).getPhone();
359            if (phone != null) {
360                phone.conference();
361            }
362        } catch (CallStateException e) {
363            Log.e(this, e, "Exception thrown trying to merge call into a conference");
364        }
365    }
366
367    /**
368     * Invoked when the conference should be put on hold.
369     */
370    @Override
371    public void onHold() {
372        if (mConferenceHost == null) {
373            return;
374        }
375        mConferenceHost.performHold();
376    }
377
378    /**
379     * Invoked when the conference should be moved from hold to active.
380     */
381    @Override
382    public void onUnhold() {
383        if (mConferenceHost == null) {
384            return;
385        }
386        mConferenceHost.performUnhold();
387    }
388
389    /**
390     * Invoked to play a DTMF tone.
391     *
392     * @param c A DTMF character.
393     */
394    @Override
395    public void onPlayDtmfTone(char c) {
396        if (mConferenceHost == null) {
397            return;
398        }
399        mConferenceHost.onPlayDtmfTone(c);
400    }
401
402    /**
403     * Invoked to stop playing a DTMF tone.
404     */
405    @Override
406    public void onStopDtmfTone() {
407        if (mConferenceHost == null) {
408            return;
409        }
410        mConferenceHost.onStopDtmfTone();
411    }
412
413    /**
414     * Handles the addition of connections to the {@link ImsConference}.  The
415     * {@link ImsConferenceController} does not add connections to the conference.
416     *
417     * @param connection The newly added connection.
418     */
419    @Override
420    public void onConnectionAdded(android.telecom.Connection connection) {
421        // No-op
422    }
423
424    private int applyCapability(int capabilities, int capability) {
425        int newCapabilities = capabilities | capability;
426        return newCapabilities;
427    }
428
429    private int removeCapability(int capabilities, int capability) {
430        int newCapabilities = capabilities & ~capability;
431        return newCapabilities;
432    }
433
434    /**
435     * Determines if this conference is hosted on the current device or the peer device.
436     *
437     * @return {@code true} if this conference is hosted on the current device, {@code false} if it
438     *      is hosted on the peer device.
439     */
440    public boolean isConferenceHost() {
441        if (mConferenceHost == null) {
442            return false;
443        }
444        com.android.internal.telephony.Connection originalConnection =
445                mConferenceHost.getOriginalConnection();
446        if (!(originalConnection instanceof ImsPhoneConnection)) {
447            return false;
448        }
449
450        ImsPhoneConnection imsPhoneConnection = (ImsPhoneConnection) originalConnection;
451        return imsPhoneConnection.isMultiparty() && imsPhoneConnection.isConferenceHost();
452    }
453
454    /**
455     * Updates the manage conference capability of the conference.  Where there are one or more
456     * conference event package participants, the conference management is permitted.  Where there
457     * are no conference event package participants, conference management is not permitted.
458     * <p>
459     * Note: We add and remove {@link Connection#CAPABILITY_CONFERENCE_HAS_NO_CHILDREN} to ensure
460     * that the conference is represented appropriately on Bluetooth devices.
461     */
462    private void updateManageConference() {
463        boolean couldManageConference = can(Connection.CAPABILITY_MANAGE_CONFERENCE);
464        boolean canManageConference = !mConferenceParticipantConnections.isEmpty();
465        Log.v(this, "updateManageConference was :%s is:%s", couldManageConference ? "Y" : "N",
466                canManageConference ? "Y" : "N");
467
468        if (couldManageConference != canManageConference) {
469            int capabilities = getConnectionCapabilities();
470
471            if (canManageConference) {
472                capabilities |= Connection.CAPABILITY_MANAGE_CONFERENCE;
473                capabilities &= ~Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
474            } else {
475                capabilities &= ~Connection.CAPABILITY_MANAGE_CONFERENCE;
476                capabilities |= Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
477            }
478
479            setConnectionCapabilities(capabilities);
480        }
481    }
482
483    /**
484     * Sets the connection hosting the conference and registers for callbacks.
485     *
486     * @param conferenceHost The connection hosting the conference.
487     */
488    private void setConferenceHost(TelephonyConnection conferenceHost) {
489        if (Log.VERBOSE) {
490            Log.v(this, "setConferenceHost " + conferenceHost);
491        }
492
493        mConferenceHost = conferenceHost;
494        mConferenceHost.addConnectionListener(mConferenceHostListener);
495        mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
496        setState(mConferenceHost.getState());
497        updateStatusHints();
498    }
499
500    /**
501     * Handles state changes for conference participant(s).  The participants data passed in
502     *
503     * @param parent The connection which was notified of the conference participant.
504     * @param participants The conference participant information.
505     */
506    private void handleConferenceParticipantsUpdate(
507            TelephonyConnection parent, List<ConferenceParticipant> participants) {
508
509        if (participants == null) {
510            return;
511        }
512        boolean newParticipantsAdded = false;
513        boolean oldParticipantsRemoved = false;
514        ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
515        HashSet<Uri> participantUserEntities = new HashSet<>(participants.size());
516
517        // Add any new participants and update existing.
518        for (ConferenceParticipant participant : participants) {
519            Uri userEntity = participant.getHandle();
520
521            participantUserEntities.add(userEntity);
522            if (!mConferenceParticipantConnections.containsKey(userEntity)) {
523                createConferenceParticipantConnection(parent, participant);
524                newParticipants.add(participant);
525                newParticipantsAdded = true;
526            } else {
527                ConferenceParticipantConnection connection =
528                        mConferenceParticipantConnections.get(userEntity);
529                connection.updateState(participant.getState());
530            }
531        }
532
533        // Set state of new participants.
534        if (newParticipantsAdded) {
535            // Set the state of the new participants at once and add to the conference
536            for (ConferenceParticipant newParticipant : newParticipants) {
537                ConferenceParticipantConnection connection =
538                        mConferenceParticipantConnections.get(newParticipant.getHandle());
539                connection.updateState(newParticipant.getState());
540            }
541        }
542
543        // Finally, remove any participants from the conference that no longer exist in the
544        // conference event package data.
545        Iterator<Map.Entry<Uri, ConferenceParticipantConnection>> entryIterator =
546                mConferenceParticipantConnections.entrySet().iterator();
547        while (entryIterator.hasNext()) {
548            Map.Entry<Uri, ConferenceParticipantConnection> entry = entryIterator.next();
549
550            if (!participantUserEntities.contains(entry.getKey())) {
551                ConferenceParticipantConnection participant = entry.getValue();
552                participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
553                participant.removeConnectionListener(mParticipantListener);
554                mTelephonyConnectionService.removeConnection(participant);
555                removeConnection(participant);
556                entryIterator.remove();
557                oldParticipantsRemoved = true;
558            }
559        }
560
561        // If new participants were added or old ones were removed, we need to ensure the state of
562        // the manage conference capability is updated.
563        if (newParticipantsAdded || oldParticipantsRemoved) {
564            updateManageConference();
565        }
566    }
567
568    /**
569     * Creates a new {@link ConferenceParticipantConnection} to represent a
570     * {@link ConferenceParticipant}.
571     * <p>
572     * The new connection is added to the conference controller and connection service.
573     *
574     * @param parent The connection which was notified of the participant change (e.g. the
575     *                         parent connection).
576     * @param participant The conference participant information.
577     */
578    private void createConferenceParticipantConnection(
579            TelephonyConnection parent, ConferenceParticipant participant) {
580
581        // Create and add the new connection in holding state so that it does not become the
582        // active call.
583        ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
584                parent.getOriginalConnection(), participant);
585        connection.addConnectionListener(mParticipantListener);
586        connection.setConnectTimeMillis(parent.getConnectTimeMillis());
587
588        if (Log.VERBOSE) {
589            Log.v(this, "createConferenceParticipantConnection: %s", connection);
590        }
591
592        mConferenceParticipantConnections.put(participant.getHandle(), connection);
593        PhoneAccountHandle phoneAccountHandle =
594                PhoneUtils.makePstnPhoneAccountHandle(parent.getPhone());
595        mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, connection);
596        addConnection(connection);
597    }
598
599    /**
600     * Removes a conference participant from the conference.
601     *
602     * @param participant The participant to remove.
603     */
604    private void removeConferenceParticipant(ConferenceParticipantConnection participant) {
605        Log.d(this, "removeConferenceParticipant: %s", participant);
606
607        participant.removeConnectionListener(mParticipantListener);
608        mConferenceParticipantConnections.remove(participant.getUserEntity());
609        mTelephonyConnectionService.removeConnection(participant);
610    }
611
612    /**
613     * Disconnects all conference participants from the conference.
614     */
615    private void disconnectConferenceParticipants() {
616        Log.v(this, "disconnectConferenceParticipants");
617
618        for (ConferenceParticipantConnection connection :
619                mConferenceParticipantConnections.values()) {
620
621            connection.removeConnectionListener(mParticipantListener);
622            // Mark disconnect cause as cancelled to ensure that the call is not logged in the
623            // call log.
624            connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
625            mTelephonyConnectionService.removeConnection(connection);
626            connection.destroy();
627        }
628        mConferenceParticipantConnections.clear();
629    }
630
631    /**
632     * Handles a change in the original connection backing the conference host connection.  This can
633     * happen if an SRVCC event occurs on the original IMS connection, requiring a fallback to
634     * GSM or CDMA.
635     * <p>
636     * If this happens, we will add the conference host connection to telecom and tear down the
637     * conference.
638     */
639    private void handleOriginalConnectionChange() {
640        if (mConferenceHost == null) {
641            Log.w(this, "handleOriginalConnectionChange; conference host missing.");
642            return;
643        }
644
645        com.android.internal.telephony.Connection originalConnection =
646                mConferenceHost.getOriginalConnection();
647
648        if (!(originalConnection instanceof ImsPhoneConnection)) {
649            if (Log.VERBOSE) {
650                Log.v(this,
651                        "Original connection for conference host is no longer an IMS connection; " +
652                                "new connection: %s", originalConnection);
653            }
654
655            PhoneAccountHandle phoneAccountHandle =
656                    PhoneUtils.makePstnPhoneAccountHandle(mConferenceHost.getPhone());
657            if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
658                GsmConnection c = new GsmConnection(originalConnection);
659                c.updateState();
660                mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, c);
661                mTelephonyConnectionService.addConnectionToConferenceController(c);
662            } // CDMA case not applicable for SRVCC
663            mConferenceHost.removeConnectionListener(mConferenceHostListener);
664            mConferenceHost.removeTelephonyConnectionListener(mTelephonyConnectionListener);
665            mConferenceHost = null;
666            setDisconnected(new DisconnectCause(DisconnectCause.OTHER));
667            disconnectConferenceParticipants();
668            destroy();
669        }
670
671        updateStatusHints();
672    }
673
674    /**
675     * Changes the state of the Ims conference.
676     *
677     * @param state the new state.
678     */
679    public void setState(int state) {
680        Log.v(this, "setState %s", Connection.stateToString(state));
681
682        switch (state) {
683            case Connection.STATE_INITIALIZING:
684            case Connection.STATE_NEW:
685            case Connection.STATE_RINGING:
686                // No-op -- not applicable.
687                break;
688            case Connection.STATE_DIALING:
689                setDialing();
690                break;
691            case Connection.STATE_DISCONNECTED:
692                DisconnectCause disconnectCause;
693                if (mConferenceHost == null) {
694                    disconnectCause = new DisconnectCause(DisconnectCause.CANCELED);
695                } else {
696                    disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause(
697                            mConferenceHost.getOriginalConnection().getDisconnectCause());
698                }
699                setDisconnected(disconnectCause);
700                destroy();
701                break;
702            case Connection.STATE_ACTIVE:
703                setActive();
704                break;
705            case Connection.STATE_HOLDING:
706                setOnHold();
707                break;
708        }
709    }
710
711    private void updateStatusHints() {
712        if (mConferenceHost == null) {
713            setStatusHints(null);
714            return;
715        }
716
717        if (mConferenceHost.isWifi()) {
718            Phone phone = mConferenceHost.getPhone();
719            if (phone != null) {
720                Context context = phone.getContext();
721                setStatusHints(new StatusHints(
722                        context.getString(R.string.status_hint_label_wifi_call),
723                        Icon.createWithResource(
724                                context.getResources(),
725                                R.drawable.ic_signal_wifi_4_bar_24dp),
726                        null /* extras */));
727            }
728        } else {
729            setStatusHints(null);
730        }
731    }
732
733    /**
734     * Builds a string representation of the {@link ImsConference}.
735     *
736     * @return String representing the conference.
737     */
738    public String toString() {
739        StringBuilder sb = new StringBuilder();
740        sb.append("[ImsConference objId:");
741        sb.append(System.identityHashCode(this));
742        sb.append(" state:");
743        sb.append(Connection.stateToString(getState()));
744        sb.append(" hostConnection:");
745        sb.append(mConferenceHost);
746        sb.append(" participants:");
747        sb.append(mConferenceParticipantConnections.size());
748        sb.append("]");
749        return sb.toString();
750    }
751}
752